richard pushed to branch tor-browser-115.10.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits: 795b62a7 by Henry Wilkes at 2024-04-25T10:32:13+01:00 fixup! Bug 30237: Add v3 onion services client authentication prompt
Bug 42542: Remove the OnionServicesAuthPrompt class.
The class is merged into OnionAuthPrompt. This currently only works when only one tab triggers the prompt at a time.
Not linted to improve commit readability.
- - - - - 38d07333 by Henry Wilkes at 2024-04-25T10:32:14+01:00 fixup! Bug 30237: Add v3 onion services client authentication prompt
Bug 42542: Lint after removing OnionServicesAuthPrompt class.
- - - - - a35c7a68 by Henry Wilkes at 2024-04-25T11:55:07+01:00 fixup! Bug 30237: Add v3 onion services client authentication prompt
Bug 42542: Re-handle showing errors.
Call "_showWarning" rather than "show" when we have an error.
Do not attempt to show the prompt if we cannot determine the onion service id.
For the service id regex, use "^(.*.)?" instead of "^(.*.)*": since the ".*" is greedy, this can only ever match up to once.
- - - - - df3374c4 by Henry Wilkes at 2024-04-30T11:11:39+01:00 fixup! Bug 30237: Add v3 onion services client authentication prompt
Bug 42542: Reset the authentication prompt when switching between two authentication tabs.
We keep a record of which tab details are being shown in the notification popup. We reset the prompt whenever we want to show different details.
We also fetch elements and set event listeners (once) when we initialize OnionAuthPrompt. In particular, the listeners should only react to the shown details.
We also assume that elements with an ID exist in the DOM.
- - - - - 0cacad0c by Henry Wilkes at 2024-04-30T11:11:46+01:00 fixup! Bug 30237: Add v3 onion services client authentication prompt
Bug 42542: Follow recent coding conventions.
Do not prefix variable names with "a" and use triple equality.
- - - - - 82fa0a97 by Henry Wilkes at 2024-04-30T11:11:46+01:00 fixup! Bug 30237: Add v3 onion services client authentication prompt
Bug 42542: Use "keydown" instead of deprecated "keypress".
Also, stop handling "Escape" key since it is already handled by PopupNotification.
- - - - - 78107767 by Henry Wilkes at 2024-04-30T11:11:47+01:00 fixup! Bug 30237: Add v3 onion services client authentication prompt
Bug 42542: Add documentation to OnionAuthPrompt.
- - - - - 170cb7ab by Henry Wilkes at 2024-04-30T11:11:47+01:00 fixup! Bug 30237: Add v3 onion services client authentication prompt
Bug 42542: Add a logger to OnionAuthPrompt.
- - - - -
2 changed files:
- browser/components/onionservices/content/authPopup.inc.xhtml - browser/components/onionservices/content/authPrompt.js
Changes:
===================================== browser/components/onionservices/content/authPopup.inc.xhtml ===================================== @@ -5,7 +5,9 @@ <description id="tor-clientauth-notification-desc"/> <label id="tor-clientauth-notification-learnmore" class="text-link popup-notification-learnmore-link" - is="text-link"/> + is="text-link" + href="about:manual#onion-services_onion-service-authentication" + useoriginprincipal="true"/> html:div <html:input id="tor-clientauth-notification-key" type="password"/> <html:div id="tor-clientauth-warning"/>
===================================== browser/components/onionservices/content/authPrompt.js ===================================== @@ -2,350 +2,417 @@
"use strict";
-const OnionAuthPrompt = (function () { +var OnionAuthPrompt = { // Only import to our internal scope, rather than the global scope of // browser.xhtml. - const lazy = {}; - ChromeUtils.defineESModuleGetters(lazy, { - TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs", - TorStrings: "resource://gre/modules/TorStrings.sys.mjs", - CommonUtils: "resource://services-common/utils.sys.mjs", - }); - - // OnionServicesAuthPrompt objects run within the main/chrome process. - // aReason is the topic passed within the observer notification that is - // causing this auth prompt to be displayed. - function OnionServicesAuthPrompt(aBrowser, aFailedURI, aReason, aOnionName) { - this._browser = aBrowser; - this._failedURI = aFailedURI; - this._reasonForPrompt = aReason; - this._onionHostname = aOnionName; - } - - const topics = { + _lazy: {}, + + /** + * The topics to listen to. + * + * @type {Object<string, string>} + */ + _topics: { clientAuthMissing: "tor-onion-services-clientauth-missing", clientAuthIncorrect: "tor-onion-services-clientauth-incorrect", - }; - - OnionServicesAuthPrompt.prototype = { - show(aWarningMessage) { - let mainAction = { - label: lazy.TorStrings.onionServices.authPrompt.done, - accessKey: lazy.TorStrings.onionServices.authPrompt.doneAccessKey, - leaveOpen: true, // Callback is responsible for closing the notification. - callback: this._onDone.bind(this), - }; - - let dialogBundle = Services.strings.createBundle( - "chrome://global/locale/dialog.properties" - ); - - let cancelAccessKey = dialogBundle.GetStringFromName("accesskey-cancel"); - if (!cancelAccessKey) { - cancelAccessKey = "c"; - } // required by PopupNotifications.show() - - let cancelAction = { - label: dialogBundle.GetStringFromName("button-cancel"), - accessKey: cancelAccessKey, - callback: this._onCancel.bind(this), - }; - - let _this = this; - let options = { - autofocus: true, - hideClose: true, - persistent: true, - removeOnDismissal: false, - eventCallback(aTopic) { - if (aTopic === "showing") { - _this._onPromptShowing(aWarningMessage); - } else if (aTopic === "shown") { - _this._onPromptShown(); - } else if (aTopic === "removed") { - _this._onPromptRemoved(); - } - }, - }; - - this._prompt = PopupNotifications.show( - this._browser, - "tor-clientauth", - "", - "tor-clientauth-notification-icon", - mainAction, - [cancelAction], - options - ); - }, - - _onPromptShowing(aWarningMessage) { - let xulDoc = this._browser.ownerDocument; - let descElem = xulDoc.getElementById("tor-clientauth-notification-desc"); - if (descElem) { - // Handle replacement of the onion name within the localized - // string ourselves so we can show the onion name as bold text. - // We do this by splitting the localized string and creating - // several HTML <span> elements. - const fmtString = lazy.TorStrings.onionServices.authPrompt.description; - const [prefix, suffix] = fmtString.split("%S"); - - const domainEl = xulDoc.createElement("span"); - domainEl.id = "tor-clientauth-notification-onionname"; - domainEl.textContent = TorUIUtils.shortenOnionAddress( - this._onionHostname - ); - - descElem.replaceChildren(prefix, domainEl, suffix); - } - - // Set "Learn More" label and href. - let learnMoreElem = xulDoc.getElementById( - "tor-clientauth-notification-learnmore" - ); - if (learnMoreElem) { - learnMoreElem.setAttribute( - "value", - lazy.TorStrings.onionServices.learnMore - ); - learnMoreElem.setAttribute( - "href", - "about:manual#onion-services_onion-service-authentication" - ); - learnMoreElem.setAttribute("useoriginprincipal", "true"); - } - - this._showWarning(aWarningMessage); - let checkboxElem = this._getCheckboxElement(); - if (checkboxElem) { - checkboxElem.checked = false; - } - }, - - _onPromptShown() { - let keyElem = this._getKeyElement(); - if (keyElem) { - keyElem.setAttribute( - "placeholder", - lazy.TorStrings.onionServices.authPrompt.keyPlaceholder - ); - this._boundOnKeyFieldKeyPress = this._onKeyFieldKeyPress.bind(this); - this._boundOnKeyFieldInput = this._onKeyFieldInput.bind(this); - keyElem.addEventListener("keypress", this._boundOnKeyFieldKeyPress); - keyElem.addEventListener("input", this._boundOnKeyFieldInput); - keyElem.focus(); - } - }, - - _onPromptRemoved() { - if (this._boundOnKeyFieldKeyPress) { - let keyElem = this._getKeyElement(); - if (keyElem) { - keyElem.value = ""; - keyElem.removeEventListener( - "keypress", - this._boundOnKeyFieldKeyPress - ); - this._boundOnKeyFieldKeyPress = undefined; - keyElem.removeEventListener("input", this._boundOnKeyFieldInput); - this._boundOnKeyFieldInput = undefined; + }, + + /** + * @typedef {object} PromptDetails + * + * @property {Browser} browser - The browser this prompt is for. + * @property {string} cause - The notification that cause this prompt. + * @property {string} onionHost - The onion host name. + * @property {nsIURI} uri - The browser URI when the notification was + * triggered. + * @property {string} onionServiceId - The onion service ID for this host. + * @property {Notification} [notification] - The notification instance for + * this prompt. + */ + + /** + * The currently shown details in the prompt. + */ + _shownDetails: null, + + /** + * Used for logging to represent PromptDetails. + * + * @param {PromptDetails} details - The details to represent. + * @returns {string} - The representation of these details. + */ + _detailsRepr(details) { + if (!details) { + return "none"; + } + return `${details.browser.browserId}:${details.onionHost}`; + }, + + /** + * Show a new prompt, using the given details. + * + * @param {PromptDetails} details - The details to show. + */ + show(details) { + this._logger.debug(`New Notification: ${this._detailsRepr(details)}`); + + let mainAction = { + label: this.TorStrings.onionServices.authPrompt.done, + accessKey: this.TorStrings.onionServices.authPrompt.doneAccessKey, + leaveOpen: true, // Callback is responsible for closing the notification. + callback: this._onDone.bind(this), + }; + + let dialogBundle = Services.strings.createBundle( + "chrome://global/locale/dialog.properties" + ); + + let cancelAccessKey = dialogBundle.GetStringFromName("accesskey-cancel"); + if (!cancelAccessKey) { + cancelAccessKey = "c"; + } // required by PopupNotifications.show() + + // The first secondarybuttoncommand (cancelAction) should be triggered when + // the user presses "Escape". + let cancelAction = { + label: dialogBundle.GetStringFromName("button-cancel"), + accessKey: cancelAccessKey, + callback: this._onCancel.bind(this), + }; + + let options = { + autofocus: true, + hideClose: true, + persistent: true, + removeOnDismissal: false, + eventCallback: topic => { + if (topic === "showing") { + this._onPromptShowing(details); + } else if (topic === "shown") { + this._onPromptShown(); + } else if (topic === "removed") { + this._onPromptRemoved(details); } + }, + }; + + details.notification = PopupNotifications.show( + details.browser, + "tor-clientauth", + "", + "tor-clientauth-notification-icon", + mainAction, + [cancelAction], + options + ); + }, + + /** + * Callback when the prompt is about to be shown. + * + * @param {PromptDetails?} details - The details to show, or null to shown + * none. + */ + _onPromptShowing(details) { + if (details === this._shownDetails) { + // The last shown details match this one exactly. + // This happens when we switch tabs to a page that has no prompt and then + // switch back. + // We don't want to reset the current state in this case. + // In particular, we keep the current _keyInput value and _persistCheckbox + // the same. + this._logger.debug(`Already showing: ${this._detailsRepr(details)}`); + return; + } + + this._logger.debug(`Now showing: ${this._detailsRepr(details)}`); + + this._shownDetails = details; + + // Clear the key input. + // In particular, clear the input when switching tabs. + this._keyInput.value = ""; + this._persistCheckbox.checked = false; + + // Handle replacement of the onion name within the localized + // string ourselves so we can show the onion name as bold text. + // We do this by splitting the localized string and creating + // several HTML <span> elements. + const fmtString = this.TorStrings.onionServices.authPrompt.description; + const [prefix, suffix] = fmtString.split("%S"); + + const domainEl = document.createElement("span"); + domainEl.id = "tor-clientauth-notification-onionname"; + domainEl.textContent = TorUIUtils.shortenOnionAddress( + this._shownDetails?.onionHost ?? "" + ); + + this._descriptionEl.replaceChildren(prefix, domainEl, suffix); + + this._showWarning(undefined); + }, + + /** + * Callback after the prompt is shown. + */ + _onPromptShown() { + this._keyInput.focus(); + }, + + /** + * Callback when a Notification is removed. + * + * @param {PromptDetails} details - The details for the removed notification. + */ + _onPromptRemoved(details) { + if (details !== this._shownDetails) { + // Removing the notification for some other page. + // For example, closing another tab that also requires authentication. + this._logger.debug(`Removed not shown: ${this._detailsRepr(details)}`); + return; + } + this._logger.debug(`Removed shown: ${this._detailsRepr(details)}`); + // Reset the prompt as a precaution. + // In particular, we want to clear the input so that the entered key does + // not persist. + this._onPromptShowing(null); + }, + + /** + * Callback when the user submits the key. + */ + async _onDone() { + this._logger.debug( + `Sumbitting key: ${this._detailsRepr(this._shownDetails)}` + ); + + // Grab the details before they might change as we await. + const { browser, onionServiceId, notification } = this._shownDetails; + const isPermanent = this._persistCheckbox.checked; + + const base64key = this._keyToBase64(this._keyInput.value); + if (!base64key) { + this._showWarning(this.TorStrings.onionServices.authPrompt.invalidKey); + return; + } + + try { + const provider = await this._lazy.TorProviderBuilder.build(); + await provider.onionAuthAdd(onionServiceId, base64key, isPermanent); + } catch (e) { + if (e.torMessage) { + this._showWarning(e.torMessage); + } else { + this._logger.error(`Failed to set key for ${onionServiceId}`, e); + this._showWarning( + this.TorStrings.onionServices.authPrompt.failedToSetKey + ); } - }, - - _onKeyFieldKeyPress(aEvent) { - if (aEvent.keyCode == aEvent.DOM_VK_RETURN) { - this._onDone(); - } else if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) { - this._prompt.remove(); - this._onCancel(); - } - }, - - _onKeyFieldInput(aEvent) { - this._showWarning(undefined); // Remove the warning. - }, - - async _onDone() { - const keyElem = this._getKeyElement(); - if (!keyElem) { - return; - } - - const base64key = this._keyToBase64(keyElem.value); - if (!base64key) { - this._showWarning(lazy.TorStrings.onionServices.authPrompt.invalidKey); - return; - } - - this._prompt.remove(); - - const controllerFailureMsg = - lazy.TorStrings.onionServices.authPrompt.failedToSetKey; + return; + } + + notification.remove(); + // Success! Reload the page. + browser.sendMessageToActor("Browser:Reload", {}, "BrowserTab"); + }, + + /** + * Callback when the user dismisses the prompt. + */ + _onCancel() { + // Arrange for an error page to be displayed: + // we build a short script calling docShell.displayError() + // and we pass it as a data: URI to loadFrameScript(), + // which runs it in the content frame which triggered + // this authentication prompt. + this._logger.debug(`Cancelling: ${this._detailsRepr(this._shownDetails)}`); + + const { browser, cause, uri } = this._shownDetails; + const errorCode = + cause === this._topics.clientAuthMissing + ? Cr.NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH + : Cr.NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH; + const io = + 'ChromeUtils.import("resource://gre/modules/Services.jsm").Services.io'; + + browser.messageManager.loadFrameScript( + `data:application/javascript,${encodeURIComponent( + `docShell.displayLoadError(${errorCode}, ${io}.newURI(${JSON.stringify( + uri.spec + )}), undefined, undefined);` + )}`, + false + ); + }, + + /** + * Show a warning message to the user or clear the warning. + * + * @param {string?} warningMessage - The message to show, or undefined to + * clear the current message. + */ + _showWarning(warningMessage) { + this._logger.debug(`Showing warning: ${warningMessage}`); + if (warningMessage) { + this._warningEl.textContent = warningMessage; + this._warningEl.removeAttribute("hidden"); + this._keyInput.classList.add("invalid"); + } else { + this._warningEl.setAttribute("hidden", "true"); + this._keyInput.classList.remove("invalid"); + } + }, + + /** + * Convert the user-entered key into base64. + * + * @param {string} keyString - The key to convert. + * @returns {string?} - The base64 representation, or undefined if the given + * key was not the correct format. + */ + _keyToBase64(keyString) { + if (!keyString) { + return undefined; + } + + let base64key; + if (keyString.length === 52) { + // The key is probably base32-encoded. Attempt to decode. + // Although base32 specifies uppercase letters, we accept lowercase + // as well because users may type in lowercase or copy a key out of + // a tor onion-auth file (which uses lowercase). + let rawKey; try { - // ^(subdomain.)*onionserviceid.onion$ (case-insensitive) - const onionServiceIdRegExp = - /^(.*.)*(?<onionServiceId>[a-z2-7]{56}).onion$/i; - // match() will return null on bad match, causing throw - const onionServiceId = this._onionHostname - .match(onionServiceIdRegExp) - .groups.onionServiceId.toLowerCase(); - - const checkboxElem = this._getCheckboxElement(); - const isPermanent = checkboxElem && checkboxElem.checked; - const provider = await lazy.TorProviderBuilder.build(); - await provider.onionAuthAdd(onionServiceId, base64key, isPermanent); - // Success! Reload the page. - this._browser.sendMessageToActor("Browser:Reload", {}, "BrowserTab"); - } catch (e) { - if (e.torMessage) { - this.show(e.torMessage); - } else { - console.error(controllerFailureMsg, e); - this.show(controllerFailureMsg); - } - } - }, - - _onCancel() { - // Arrange for an error page to be displayed: - // we build a short script calling docShell.displayError() - // and we pass it as a data: URI to loadFrameScript(), - // which runs it in the content frame which triggered - // this authentication prompt. - const failedURI = this._failedURI.spec; - const errorCode = - this._reasonForPrompt === topics.clientAuthMissing - ? Cr.NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH - : Cr.NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH; - const io = - 'ChromeUtils.import("resource://gre/modules/Services.jsm").Services.io'; - - this._browser.messageManager.loadFrameScript( - `data:application/javascript,${encodeURIComponent( - `docShell.displayLoadError(${errorCode}, ${io}.newURI(${JSON.stringify( - failedURI - )}), undefined, undefined);` - )}`, - false - ); - }, - - _getKeyElement() { - let xulDoc = this._browser.ownerDocument; - return xulDoc.getElementById("tor-clientauth-notification-key"); - }, - - _getCheckboxElement() { - let xulDoc = this._browser.ownerDocument; - return xulDoc.getElementById("tor-clientauth-persistkey-checkbox"); - }, - - _showWarning(aWarningMessage) { - let xulDoc = this._browser.ownerDocument; - let warningElem = xulDoc.getElementById("tor-clientauth-warning"); - let keyElem = this._getKeyElement(); - if (warningElem) { - if (aWarningMessage) { - warningElem.textContent = aWarningMessage; - warningElem.removeAttribute("hidden"); - if (keyElem) { - keyElem.className = "invalid"; - } - } else { - warningElem.setAttribute("hidden", "true"); - if (keyElem) { - keyElem.className = ""; - } - } - } - }, - - // Returns undefined if the key is the wrong length or format. - _keyToBase64(aKeyString) { - if (!aKeyString) { - return undefined; - } + rawKey = this._lazy.CommonUtils.decodeBase32(keyString.toUpperCase()); + } catch (e) {}
- let base64key; - if (aKeyString.length == 52) { - // The key is probably base32-encoded. Attempt to decode. - // Although base32 specifies uppercase letters, we accept lowercase - // as well because users may type in lowercase or copy a key out of - // a tor onion-auth file (which uses lowercase). - let rawKey; + if (rawKey) { try { - rawKey = lazy.CommonUtils.decodeBase32(aKeyString.toUpperCase()); + base64key = btoa(rawKey); } catch (e) {} - - if (rawKey) { - try { - base64key = btoa(rawKey); - } catch (e) {} - } - } else if ( - aKeyString.length == 44 && - /^[a-zA-Z0-9+/]*=*$/.test(aKeyString) - ) { - // The key appears to be a correctly formatted base64 value. If not, - // tor will return an error when we try to add the key via the - // control port. - base64key = aKeyString; } - - return base64key; - }, - }; - - let retval = { - init() { - Services.obs.addObserver(this, topics.clientAuthMissing); - Services.obs.addObserver(this, topics.clientAuthIncorrect); - }, - - uninit() { - Services.obs.removeObserver(this, topics.clientAuthMissing); - Services.obs.removeObserver(this, topics.clientAuthIncorrect); - }, - - // aSubject is the DOM Window or browser where the prompt should be shown. - // aData contains the .onion name. - observe(aSubject, aTopic, aData) { - if ( - aTopic != topics.clientAuthMissing && - aTopic != topics.clientAuthIncorrect - ) { - return; - } - - let browser; - if (aSubject instanceof Ci.nsIDOMWindow) { - let contentWindow = aSubject.QueryInterface(Ci.nsIDOMWindow); - browser = contentWindow.docShell.chromeEventHandler; - } else { - browser = aSubject.QueryInterface(Ci.nsIBrowser); - } - - if (!gBrowser.browsers.some(aBrowser => aBrowser == browser)) { - return; // This window does not contain the subject browser; ignore. + } else if ( + keyString.length === 44 && + /^[a-zA-Z0-9+/]*=*$/.test(keyString) + ) { + // The key appears to be a correctly formatted base64 value. If not, + // tor will return an error when we try to add the key via the + // control port. + base64key = keyString; + } + + return base64key; + }, + + /** + * Initialize the authentication prompt. + */ + init() { + this._logger = console.createInstance({ + prefix: "OnionAuthPrompt", + maxLogLevel: "Warn", + maxLogLevelPref: "browser.onionAuthPrompt.loglevel", + }); + + const { TorStrings } = ChromeUtils.importESModule( + "resource://gre/modules/TorStrings.sys.mjs" + ); + this.TorStrings = TorStrings; + ChromeUtils.defineESModuleGetters(this._lazy, { + TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs", + CommonUtils: "resource://services-common/utils.sys.mjs", + }); + + this._keyInput = document.getElementById("tor-clientauth-notification-key"); + this._persistCheckbox = document.getElementById( + "tor-clientauth-persistkey-checkbox" + ); + this._warningEl = document.getElementById("tor-clientauth-warning"); + this._descriptionEl = document.getElementById( + "tor-clientauth-notification-desc" + ); + + // Set "Learn More" label and href. + const learnMoreElem = document.getElementById( + "tor-clientauth-notification-learnmore" + ); + learnMoreElem.setAttribute( + "value", + this.TorStrings.onionServices.learnMore + ); + + this._keyInput.setAttribute( + "placeholder", + this.TorStrings.onionServices.authPrompt.keyPlaceholder + ); + this._keyInput.addEventListener("keydown", event => { + if (event.key === "Enter") { + event.preventDefault(); + this._onDone(); } - - let failedURI = browser.currentURI; - let authPrompt = new OnionServicesAuthPrompt( - browser, - failedURI, - aTopic, - aData + }); + this._keyInput.addEventListener("input", event => { + // Remove the warning. + this._showWarning(undefined); + }); + + Services.obs.addObserver(this, this._topics.clientAuthMissing); + Services.obs.addObserver(this, this._topics.clientAuthIncorrect); + }, + + /** + * Un-initialize the authentication prompt. + */ + uninit() { + Services.obs.removeObserver(this, this._topics.clientAuthMissing); + Services.obs.removeObserver(this, this._topics.clientAuthIncorrect); + }, + + observe(subject, topic, data) { + if ( + topic !== this._topics.clientAuthMissing && + topic !== this._topics.clientAuthIncorrect + ) { + return; + } + + // "subject" is the DOM window or browser where the prompt should be shown. + let browser; + if (subject instanceof Ci.nsIDOMWindow) { + let contentWindow = subject.QueryInterface(Ci.nsIDOMWindow); + browser = contentWindow.docShell.chromeEventHandler; + } else { + browser = subject.QueryInterface(Ci.nsIBrowser); + } + + if (!gBrowser.browsers.includes(browser)) { + // This window does not contain the subject browser. + this._logger.debug( + `Window ${window.docShell.outerWindowID}: Ignoring ${topic}` ); - authPrompt.show(undefined); - }, - }; - - return retval; -})(); /* OnionAuthPrompt */ - -Object.defineProperty(this, "OnionAuthPrompt", { - value: OnionAuthPrompt, - enumerable: true, - writable: false, -}); + return; + } + this._logger.debug( + `Window ${window.docShell.outerWindowID}: Handling ${topic}` + ); + + const onionHost = data; + // ^(subdomain.)*onionserviceid.onion$ (case-insensitive) + const onionServiceId = onionHost + .match(/^(.*.)?(?<onionServiceId>[a-z2-7]{56}).onion$/i) + ?.groups.onionServiceId.toLowerCase(); + if (!onionServiceId) { + this._logger.error(`Malformed onion address: ${onionHost}`); + return; + } + + const details = { + browser, + cause: topic, + onionHost, + uri: browser.currentURI, + onionServiceId, + }; + this.show(details); + }, +};
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/29ff3f1...