[tor-commits] [tor-browser/tor-browser-85.0b9-10.5-1] Bug 28005: Implement .onion alias urlbar rewrites
sysrqb at torproject.org
sysrqb at torproject.org
Fri Jan 15 15:14:30 UTC 2021
commit fb7f4a5574fe1f33578363bd401b397e09321980
Author: Alex Catarineu <acat at torproject.org>
Date: Thu Feb 13 13:24:33 2020 +0100
Bug 28005: Implement .onion alias urlbar rewrites
A custom HTTPS Everywhere update channel is installed,
which provides rules for locally redirecting some memorable
.tor.onion URLs to non-memorable .onion URLs.
When these redirects occur, we also rewrite the URL in the urlbar
to display the human-memorable hostname instead of the actual
.onion.
Bug 34196: Update site info URL with the onion name
---
browser/actors/ClickHandlerChild.jsm | 20 ++
browser/actors/ClickHandlerParent.jsm | 1 +
browser/actors/ContextMenuChild.jsm | 4 +
browser/base/content/browser-places.js | 12 +-
browser/base/content/browser-siteIdentity.js | 12 +-
browser/base/content/browser.js | 43 ++++-
browser/base/content/nsContextMenu.js | 18 ++
browser/base/content/pageinfo/pageInfo.js | 2 +-
browser/base/content/pageinfo/pageInfo.xhtml | 10 +
browser/base/content/pageinfo/security.js | 17 +-
browser/base/content/tabbrowser.js | 7 +
browser/base/content/utilityOverlay.js | 12 ++
browser/components/BrowserGlue.jsm | 8 +
.../onionservices/ExtensionMessaging.jsm | 77 ++++++++
.../onionservices/HttpsEverywhereControl.jsm | 119 ++++++++++++
.../components/onionservices/OnionAliasStore.jsm | 201 +++++++++++++++++++++
browser/components/onionservices/moz.build | 6 +
browser/components/urlbar/UrlbarInput.jsm | 13 +-
docshell/base/nsDocShell.cpp | 52 ++++++
docshell/base/nsDocShell.h | 6 +
docshell/base/nsDocShellLoadState.cpp | 4 +
docshell/base/nsIDocShell.idl | 5 +
docshell/base/nsIWebNavigation.idl | 5 +
docshell/shistory/SessionHistoryEntry.cpp | 14 ++
docshell/shistory/SessionHistoryEntry.h | 1 +
docshell/shistory/nsISHEntry.idl | 5 +
docshell/shistory/nsSHEntry.cpp | 22 ++-
docshell/shistory/nsSHEntry.h | 1 +
dom/interfaces/base/nsIBrowser.idl | 3 +-
dom/ipc/BrowserChild.cpp | 2 +
dom/ipc/BrowserParent.cpp | 3 +-
dom/ipc/PBrowser.ipdl | 1 +
modules/libpref/init/StaticPrefList.yaml | 6 +
netwerk/dns/effective_tld_names.dat | 2 +
netwerk/ipc/DocumentLoadListener.cpp | 10 +
toolkit/content/widgets/browser-custom-element.js | 13 +-
toolkit/modules/sessionstore/SessionHistory.jsm | 5 +
xpcom/reflect/xptinfo/xptinfo.h | 3 +-
38 files changed, 722 insertions(+), 23 deletions(-)
diff --git a/browser/actors/ClickHandlerChild.jsm b/browser/actors/ClickHandlerChild.jsm
index d5f7f31f3280..1d147bb274f2 100644
--- a/browser/actors/ClickHandlerChild.jsm
+++ b/browser/actors/ClickHandlerChild.jsm
@@ -136,6 +136,26 @@ class ClickHandlerChild extends JSWindowActorChild {
json.originStoragePrincipal = ownerDoc.effectiveStoragePrincipal;
json.triggeringPrincipal = ownerDoc.nodePrincipal;
+ // Check if the link needs to be opened with .tor.onion urlbar rewrites
+ // allowed. Only when the owner doc has onionUrlbarRewritesAllowed = true
+ // and the same origin we should allow this.
+ json.onionUrlbarRewritesAllowed = false;
+ if (this.docShell.onionUrlbarRewritesAllowed) {
+ const sm = Services.scriptSecurityManager;
+ try {
+ let targetURI = Services.io.newURI(href);
+ let isPrivateWin =
+ ownerDoc.nodePrincipal.originAttributes.privateBrowsingId > 0;
+ sm.checkSameOriginURI(
+ docshell.currentDocumentChannel.URI,
+ targetURI,
+ false,
+ isPrivateWin
+ );
+ json.onionUrlbarRewritesAllowed = true;
+ } catch (e) {}
+ }
+
// If a link element is clicked with middle button, user wants to open
// the link somewhere rather than pasting clipboard content. Therefore,
// when it's clicked with middle button, we should prevent multiple
diff --git a/browser/actors/ClickHandlerParent.jsm b/browser/actors/ClickHandlerParent.jsm
index 75509b95ce7f..06d56624e316 100644
--- a/browser/actors/ClickHandlerParent.jsm
+++ b/browser/actors/ClickHandlerParent.jsm
@@ -99,6 +99,7 @@ class ClickHandlerParent extends JSWindowActorParent {
charset: browser.characterSet,
referrerInfo: E10SUtils.deserializeReferrerInfo(data.referrerInfo),
allowMixedContent: data.allowMixedContent,
+ onionUrlbarRewritesAllowed: data.onionUrlbarRewritesAllowed,
isContentWindowPrivate: data.isContentWindowPrivate,
originPrincipal: data.originPrincipal,
originStoragePrincipal: data.originStoragePrincipal,
diff --git a/browser/actors/ContextMenuChild.jsm b/browser/actors/ContextMenuChild.jsm
index 75e50d6a356e..40bf603ddda9 100644
--- a/browser/actors/ContextMenuChild.jsm
+++ b/browser/actors/ContextMenuChild.jsm
@@ -576,6 +576,9 @@ class ContextMenuChild extends JSWindowActorChild {
// The same-origin check will be done in nsContextMenu.openLinkInTab.
let parentAllowsMixedContent = !!this.docShell.mixedContentChannel;
+ let parentAllowsOnionUrlbarRewrites = this.docShell
+ .onionUrlbarRewritesAllowed;
+
let disableSetDesktopBackground = null;
// Media related cache info parent needs for saving
@@ -688,6 +691,7 @@ class ContextMenuChild extends JSWindowActorChild {
frameBrowsingContextID,
disableSetDesktopBackground,
parentAllowsMixedContent,
+ parentAllowsOnionUrlbarRewrites,
};
if (context.inFrame && !context.inSrcdocFrame) {
diff --git a/browser/base/content/browser-places.js b/browser/base/content/browser-places.js
index d5bc2a8d40c5..7eccf991016b 100644
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -486,7 +486,8 @@ var PlacesCommandHook = {
*/
async bookmarkPage() {
let browser = gBrowser.selectedBrowser;
- let url = new URL(browser.currentURI.spec);
+ const uri = browser.currentOnionAliasURI || browser.currentURI;
+ let url = new URL(uri.spec);
let info = await PlacesUtils.bookmarks.fetch({ url });
let isNewBookmark = !info;
let showEditUI = !isNewBookmark || StarUI.showForNewBookmarks;
@@ -594,7 +595,7 @@ var PlacesCommandHook = {
tabs.forEach(tab => {
let browser = tab.linkedBrowser;
- let uri = browser.currentURI;
+ let uri = browser.currentOnionAliasURI || browser.currentURI;
let title = browser.contentTitle || tab.label;
let spec = uri.spec;
if (!(spec in uniquePages)) {
@@ -1917,14 +1918,17 @@ var BookmarkingUI = {
},
onLocationChange: function BUI_onLocationChange() {
- if (this._uri && gBrowser.currentURI.equals(this._uri)) {
+ const uri =
+ gBrowser.selectedBrowser.currentOnionAliasURI || gBrowser.currentURI;
+ if (this._uri && uri.equals(this._uri)) {
return;
}
this.updateStarState();
},
updateStarState: function BUI_updateStarState() {
- this._uri = gBrowser.currentURI;
+ this._uri =
+ gBrowser.selectedBrowser.currentOnionAliasURI || gBrowser.currentURI;
this._itemGuids.clear();
let guids = new Set();
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js
index ea6a6a0f7833..143923ce8b2a 100644
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -689,13 +689,13 @@ var gIdentityHandler = {
* nsIURI for which the identity UI should be displayed, already
* processed by createExposableURI.
*/
- updateIdentity(state, uri) {
+ updateIdentity(state, uri, onionAliasURI) {
let shouldHidePopup = this._uri && this._uri.spec != uri.spec;
this._state = state;
// Firstly, populate the state properties required to display the UI. See
// the documentation of the individual properties for details.
- this.setURI(uri);
+ this.setURI(uri, onionAliasURI);
this._secInfo = gBrowser.securityUI.secInfo;
this._isSecureContext = gBrowser.securityUI.isSecureContext;
@@ -781,17 +781,18 @@ var gIdentityHandler = {
* Attempt to provide proper IDN treatment for host names
*/
getEffectiveHost() {
+ let uri = this._onionAliasURI || this._uri;
if (!this._IDNService) {
this._IDNService = Cc["@mozilla.org/network/idn-service;1"].getService(
Ci.nsIIDNService
);
}
try {
- return this._IDNService.convertToDisplayIDN(this._uri.host, {});
+ return this._IDNService.convertToDisplayIDN(uri.host, {});
} catch (e) {
// If something goes wrong (e.g. host is an IP address) just fail back
// to the full domain.
- return this._uri.host;
+ return uri.host;
}
},
@@ -1268,11 +1269,12 @@ var gIdentityHandler = {
this.updateSitePermissions();
},
- setURI(uri) {
+ setURI(uri, onionAliasURI) {
if (uri.schemeIs("view-source")) {
uri = Services.io.newURI(uri.spec.replace(/^view-source:/i, ""));
}
this._uri = uri;
+ this._onionAliasURI = onionAliasURI;
try {
// Account for file: urls and catch when "" is the value
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 6c844a56810d..40ae64952e31 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -78,6 +78,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
Translation: "resource:///modules/translation/TranslationParent.jsm",
+ OnionAliasStore: "resource:///modules/OnionAliasStore.jsm",
UITour: "resource:///modules/UITour.jsm",
UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
UrlbarInput: "resource:///modules/UrlbarInput.jsm",
@@ -2250,6 +2251,7 @@ var gBrowserInit = {
// [9]: allowInheritPrincipal (bool)
// [10]: csp (nsIContentSecurityPolicy)
// [11]: nsOpenWindowInfo
+ // [12]: onionUrlbarRewritesAllowed (bool)
let userContextId =
window.arguments[5] != undefined
? window.arguments[5]
@@ -2269,7 +2271,8 @@ var gBrowserInit = {
// TODO fix allowInheritPrincipal to default to false.
// Default to true unless explicitly set to false because of bug 1475201.
window.arguments[9] !== false,
- window.arguments[10]
+ window.arguments[10],
+ window.arguments[12]
);
window.focus();
} else {
@@ -3169,7 +3172,8 @@ function loadURI(
forceAboutBlankViewerInCurrent,
triggeringPrincipal,
allowInheritPrincipal = false,
- csp = null
+ csp = null,
+ onionUrlbarRewritesAllowed = false
) {
if (!triggeringPrincipal) {
throw new Error("Must load with a triggering Principal");
@@ -3187,6 +3191,7 @@ function loadURI(
csp,
forceAboutBlankViewerInCurrent,
allowInheritPrincipal,
+ onionUrlbarRewritesAllowed,
});
} catch (e) {
Cu.reportError(e);
@@ -5267,11 +5272,24 @@ var XULBrowserWindow = {
this.reloadCommand.removeAttribute("disabled");
}
+ // The onion memorable alias needs to be used in gURLBar.setURI, but also in
+ // other parts of the code (like the bookmarks UI), so we save it.
+ if (gBrowser.selectedBrowser.onionUrlbarRewritesAllowed) {
+ gBrowser.selectedBrowser.currentOnionAliasURI = OnionAliasStore.getShortURI(
+ aLocationURI
+ );
+ } else {
+ gBrowser.selectedBrowser.currentOnionAliasURI = null;
+ }
+
// We want to update the popup visibility if we received this notification
// via simulated locationchange events such as switching between tabs, however
// if this is a document navigation then PopupNotifications will be updated
// via TabsProgressListener.onLocationChange and we do not want it called twice
- gURLBar.setURI(aLocationURI, aIsSimulated);
+ gURLBar.setURI(
+ gBrowser.selectedBrowser.currentOnionAliasURI || aLocationURI,
+ aIsSimulated
+ );
BookmarkingUI.onLocationChange();
// If we've actually changed document, update the toolbar visibility.
@@ -5457,6 +5475,7 @@ var XULBrowserWindow = {
// Don't need to do anything if the data we use to update the UI hasn't
// changed
let uri = gBrowser.currentURI;
+ let onionAliasURI = gBrowser.selectedBrowser.currentOnionAliasURI;
let spec = uri.spec;
let isSecureContext = gBrowser.securityUI.isSecureContext;
if (
@@ -5480,7 +5499,7 @@ var XULBrowserWindow = {
try {
uri = Services.io.createExposableURI(uri);
} catch (e) {}
- gIdentityHandler.updateIdentity(this._state, uri);
+ gIdentityHandler.updateIdentity(this._state, uri, onionAliasURI);
},
// simulate all change notifications after switching tabs
@@ -6984,6 +7003,21 @@ function handleLinkClick(event, href, linkNode) {
} catch (e) {}
}
+ // Check if the link needs to be opened with .tor.onion urlbar rewrites
+ // allowed. Only when the owner doc has onionUrlbarRewritesAllowed = true
+ // and the same origin we should allow this.
+ let persistOnionUrlbarRewritesAllowedInChildTab = false;
+ if (where == "tab" && gBrowser.docShell.onionUrlbarRewritesAllowed) {
+ const sm = Services.scriptSecurityManager;
+ try {
+ let tURI = makeURI(href);
+ let isPrivateWin =
+ doc.nodePrincipal.originAttributes.privateBrowsingId > 0;
+ sm.checkSameOriginURI(doc.documentURIObject, tURI, false, isPrivateWin);
+ persistOnionUrlbarRewritesAllowedInChildTab = true;
+ } catch (e) {}
+ }
+
let frameID = WebNavigationFrames.getFrameId(doc.defaultView);
urlSecurityCheck(href, doc.nodePrincipal);
@@ -6996,6 +7030,7 @@ function handleLinkClick(event, href, linkNode) {
triggeringPrincipal: doc.nodePrincipal,
csp: doc.csp,
frameID,
+ onionUrlbarRewritesAllowed: persistOnionUrlbarRewritesAllowedInChildTab,
};
// The new tab/window must use the same userContextId
diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js
index 31fdaae590ac..458c827b94cb 100644
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -58,6 +58,7 @@ function openContextMenu(aMessage, aBrowser, aActor) {
disableSetDesktopBackground: data.disableSetDesktopBackground,
loginFillInfo: data.loginFillInfo,
parentAllowsMixedContent: data.parentAllowsMixedContent,
+ parentAllowsOnionUrlbarRewrites: data.parentAllowsOnionUrlbarRewrites,
userContextId: data.userContextId,
webExtContextData: data.webExtContextData,
cookieJarSettings: E10SUtils.deserializeCookieJarSettings(
@@ -1067,6 +1068,7 @@ class nsContextMenu {
triggeringPrincipal: this.principal,
csp: this.csp,
frameID: this.contentData.frameID,
+ onionUrlbarRewritesAllowed: false,
};
for (let p in extra) {
params[p] = extra[p];
@@ -1090,6 +1092,22 @@ class nsContextMenu {
}
params.referrerInfo = referrerInfo;
+
+ // Check if the link needs to be opened with .tor.onion urlbar rewrites
+ // allowed. Only when parent has onionUrlbarRewritesAllowed = true
+ // and the same origin we should allow this.
+ if (this.contentData.parentAllowsOnionUrlbarRewrites) {
+ let referrerURI = this.contentData.documentURIObject;
+ const sm = Services.scriptSecurityManager;
+ try {
+ let targetURI = this.linkURI;
+ let isPrivateWin =
+ this.browser.contentPrincipal.originAttributes.privateBrowsingId > 0;
+ sm.checkSameOriginURI(referrerURI, targetURI, false, isPrivateWin);
+ params.onionUrlbarRewritesAllowed = true;
+ } catch (e) {}
+ }
+
return params;
}
diff --git a/browser/base/content/pageinfo/pageInfo.js b/browser/base/content/pageinfo/pageInfo.js
index 74a5d28a317e..15acd67dbcaf 100644
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -398,7 +398,7 @@ async function onNonMediaPageInfoLoad(browser, pageInfoData, imageInfo) {
);
}
onLoadPermission(uri, principal);
- securityOnLoad(uri, windowInfo);
+ securityOnLoad(uri, windowInfo, browser.currentOnionAliasURI);
}
function resetPageInfo(args) {
diff --git a/browser/base/content/pageinfo/pageInfo.xhtml b/browser/base/content/pageinfo/pageInfo.xhtml
index f40ffd3778d8..a23f2bb5748c 100644
--- a/browser/base/content/pageinfo/pageInfo.xhtml
+++ b/browser/base/content/pageinfo/pageInfo.xhtml
@@ -312,6 +312,16 @@
<input id="security-identity-domain-value" readonly="readonly"/>
</td>
</tr>
+ <!-- Onion Alias -->
+ <tr id="security-view-identity-onionalias-row">
+ <th>
+ <xul:label id="security-view-identity-onionalias"
+ control="security-view-identity-onionalias-value"/>
+ </th>
+ <td>
+ <input id="security-view-identity-onionalias-value" readonly="true"/>
+ </td>
+ </tr>
<!-- Owner -->
<tr>
<th>
diff --git a/browser/base/content/pageinfo/security.js b/browser/base/content/pageinfo/security.js
index 192e9f763700..7693a0304823 100644
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -249,7 +249,7 @@ var security = {
},
};
-async function securityOnLoad(uri, windowInfo) {
+async function securityOnLoad(uri, windowInfo, onionAliasURI) {
await security.init(uri, windowInfo);
let info = security.securityInfo;
@@ -262,6 +262,21 @@ async function securityOnLoad(uri, windowInfo) {
}
document.getElementById("securityTab").hidden = false;
+ if (onionAliasURI) {
+ setText(
+ "security-view-identity-onionalias",
+ gTorButtonBundle.GetStringFromName("pageInfo_OnionName")
+ );
+ setText("security-view-identity-onionalias-value", onionAliasURI.host);
+ document.getElementById(
+ "security-view-identity-onionalias-row"
+ ).hidden = false;
+ } else {
+ document.getElementById(
+ "security-view-identity-onionalias-row"
+ ).hidden = true;
+ }
+
/* Set Identity section text */
setText("security-identity-domain-value", windowInfo.hostName);
diff --git a/browser/base/content/tabbrowser.js b/browser/base/content/tabbrowser.js
index 8cc178d17a67..11513d67af7a 100644
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -1557,6 +1557,7 @@
var aRelatedToCurrent;
var aAllowInheritPrincipal;
var aAllowMixedContent;
+ var aOnionUrlbarRewritesAllowed;
var aSkipAnimation;
var aForceNotRemote;
var aPreferredRemoteType;
@@ -1587,6 +1588,7 @@
aRelatedToCurrent = params.relatedToCurrent;
aAllowInheritPrincipal = !!params.allowInheritPrincipal;
aAllowMixedContent = params.allowMixedContent;
+ aOnionUrlbarRewritesAllowed = params.onionUrlbarRewritesAllowed;
aSkipAnimation = params.skipAnimation;
aForceNotRemote = params.forceNotRemote;
aPreferredRemoteType = params.preferredRemoteType;
@@ -1628,6 +1630,7 @@
relatedToCurrent: aRelatedToCurrent,
skipAnimation: aSkipAnimation,
allowMixedContent: aAllowMixedContent,
+ onionUrlbarRewritesAllowed: aOnionUrlbarRewritesAllowed,
forceNotRemote: aForceNotRemote,
createLazyBrowser: aCreateLazyBrowser,
preferredRemoteType: aPreferredRemoteType,
@@ -2432,6 +2435,7 @@
{
allowInheritPrincipal,
allowMixedContent,
+ onionUrlbarRewritesAllowed,
allowThirdPartyFixup,
bulkOrderedOpen,
charset,
@@ -2768,6 +2772,9 @@
if (allowMixedContent) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;
}
+ if (onionUrlbarRewritesAllowed) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES;
+ }
if (!allowInheritPrincipal) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
}
diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js
index 7989f58aee69..c984cdd6a4ab 100644
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -368,6 +368,7 @@ function openLinkIn(url, where, params) {
var aRelatedToCurrent = params.relatedToCurrent;
var aAllowInheritPrincipal = !!params.allowInheritPrincipal;
var aAllowMixedContent = params.allowMixedContent;
+ var aOnionUrlbarRewritesAllowed = params.onionUrlbarRewritesAllowed;
var aForceAllowDataURI = params.forceAllowDataURI;
var aInBackground = params.inBackground;
var aInitiatingDoc = params.initiatingDoc;
@@ -484,6 +485,11 @@ function openLinkIn(url, where, params) {
].createInstance(Ci.nsISupportsPRBool);
allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup;
+ var onionUrlbarRewritesAllowed = Cc[
+ "@mozilla.org/supports-PRBool;1"
+ ].createInstance(Ci.nsISupportsPRBool);
+ onionUrlbarRewritesAllowed.data = aOnionUrlbarRewritesAllowed;
+
var userContextIdSupports = Cc[
"@mozilla.org/supports-PRUint32;1"
].createInstance(Ci.nsISupportsPRUint32);
@@ -500,6 +506,8 @@ function openLinkIn(url, where, params) {
sa.appendElement(aTriggeringPrincipal);
sa.appendElement(null); // allowInheritPrincipal
sa.appendElement(aCsp);
+ sa.appendElement(null); // nsOpenWindowInfo
+ sa.appendElement(onionUrlbarRewritesAllowed);
const sourceWindow = w || window;
let win;
@@ -617,6 +625,9 @@ function openLinkIn(url, where, params) {
if (aForceAllowDataURI) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
}
+ if (aOnionUrlbarRewritesAllowed) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES;
+ }
let { URI_INHERITS_SECURITY_CONTEXT } = Ci.nsIProtocolHandler;
if (
@@ -664,6 +675,7 @@ function openLinkIn(url, where, params) {
relatedToCurrent: aRelatedToCurrent,
skipAnimation: aSkipTabAnimation,
allowMixedContent: aAllowMixedContent,
+ onionUrlbarRewritesAllowed: aOnionUrlbarRewritesAllowed,
userContextId: aUserContextId,
originPrincipal: aPrincipal,
originStoragePrincipal: aStoragePrincipal,
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 9f0ecb8214f5..7fdacf88d168 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -80,6 +80,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
TabUnloader: "resource:///modules/TabUnloader.jsm",
TelemetryUtils: "resource://gre/modules/TelemetryUtils.jsm",
TRRRacer: "resource:///modules/TRRPerformance.jsm",
+ OnionAliasStore: "resource:///modules/OnionAliasStore.jsm",
UIState: "resource://services-sync/UIState.jsm",
WebChannel: "resource://gre/modules/WebChannel.jsm",
WindowsRegistry: "resource://gre/modules/WindowsRegistry.jsm",
@@ -2112,6 +2113,7 @@ BrowserGlue.prototype = {
Normandy.uninit();
RFPHelper.uninit();
ASRouterNewTabHook.destroy();
+ OnionAliasStore.uninit();
},
// Set up a listener to enable/disable the screenshots extension
@@ -2541,6 +2543,12 @@ BrowserGlue.prototype = {
},
},
+ {
+ task: () => {
+ OnionAliasStore.init();
+ },
+ },
+
{
task: () => {
Blocklist.loadBlocklistAsync();
diff --git a/browser/components/onionservices/ExtensionMessaging.jsm b/browser/components/onionservices/ExtensionMessaging.jsm
new file mode 100644
index 000000000000..c93b8c6edf85
--- /dev/null
+++ b/browser/components/onionservices/ExtensionMessaging.jsm
@@ -0,0 +1,77 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["ExtensionMessaging"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { ExtensionUtils } = ChromeUtils.import(
+ "resource://gre/modules/ExtensionUtils.jsm"
+);
+const { MessageChannel } = ChromeUtils.import(
+ "resource://gre/modules/MessageChannel.jsm"
+);
+const { AddonManager } = ChromeUtils.import(
+ "resource://gre/modules/AddonManager.jsm"
+);
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ ExtensionParent: "resource://gre/modules/ExtensionParent.jsm",
+});
+
+class ExtensionMessaging {
+ constructor() {
+ this._callback = null;
+ this._handlers = new Map();
+ this._messageManager = Services.cpmm;
+ }
+
+ async sendMessage(message, extensionId) {
+ const addon = await AddonManager.getAddonByID(extensionId);
+ if (!addon) {
+ throw new Error(`extension '${extensionId} does not exist`);
+ }
+ await addon.startupPromise;
+
+ const { torSendExtensionMessage } = ExtensionParent;
+ return torSendExtensionMessage(extensionId, message);
+ }
+
+ unload() {
+ if (this._callback) {
+ this._handlers.clear();
+ this._messageManager.removeMessageListener(
+ "MessageChannel:Response",
+ this._callback
+ );
+ this._callback = null;
+ }
+ }
+
+ _onMessage({ data }) {
+ const channelId = data.messageName;
+ if (this._handlers.has(channelId)) {
+ const { resolve, reject } = this._handlers.get(channelId);
+ this._handlers.delete(channelId);
+ if (data.error) {
+ reject(new Error(data.error.message));
+ } else {
+ resolve(data.value);
+ }
+ }
+ }
+
+ _init() {
+ if (this._callback === null) {
+ this._callback = this._onMessage.bind(this);
+ this._messageManager.addMessageListener(
+ "MessageChannel:Response",
+ this._callback
+ );
+ }
+ }
+}
diff --git a/browser/components/onionservices/HttpsEverywhereControl.jsm b/browser/components/onionservices/HttpsEverywhereControl.jsm
new file mode 100644
index 000000000000..60c3b5fca282
--- /dev/null
+++ b/browser/components/onionservices/HttpsEverywhereControl.jsm
@@ -0,0 +1,119 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["HttpsEverywhereControl"];
+
+const { ExtensionMessaging } = ChromeUtils.import(
+ "resource:///modules/ExtensionMessaging.jsm"
+);
+const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
+
+const EXTENSION_ID = "https-everywhere-eff at eff.org";
+const SECUREDROP_TOR_ONION_CHANNEL = {
+ name: "SecureDropTorOnion",
+ jwk: {
+ kty: "RSA",
+ e: "AQAB",
+ n:
+ "p10BbUVc5Xj2S_-MH3bACNBaISo_r9e3PVPyTTjsGsdg2qSXvqUO42fBtpFAy0zUzIGS83v4JjiRdvKJaZTIvbC8AcpymzdsTqujMm8RPTSy3hO_8mXzGa4DEsIB1uNLnUWRBKXvSGCmT9kFyxhTpkYqokNBzafVihTU34tN2Md1xFHnmZGqfYtPtbJLWAa5Z1M11EyR4lIyUxIiPTV9t1XstDbWr3iS83REJrGEFmjG1-BAgx8_lDUTa41799N2yYEhgZud7bL0M3ei8s5OERjiion5uANkUV3-s2QqUZjiVA-XR_HizXjciaUWNd683KqekpNOZ_0STh_UGwpcwU-KwG07QyiCrLrRpz8S_vH8CqGrrcWY3GSzYe9dp34jJdO65oA-G8tK6fMXtvTCFDZI6oNNaXJH71F5J0YbqO2ZqwKYc2WSi0gKVl2wd9roOVjaBmkJqvocntYuNM7t38fDEWHn5KUkmrTbiG68Cy56tDUfpKl3D9Uj4LaMvxJ1tKGvzQ4k_60odT7gIxu6DqYjXUHZpwPsSGBq3njaD7boe4CUXF2K7ViOc87BsKxRNCzDD8OklRjjXzOTOBH3PqFJ93CJ-4ECE5t9STU20aZ8E-2zKB8vjKyCySE4-kcIvBBsnkwVaJTPy9Ft1qYybo-soXEWVEZATANNWklBt8k",
+ },
+ update_path_prefix: "https://securedrop.org/https-everywhere/",
+ scope:
+ "^https?:\\/\\/[a-z0-9-]+(?:\\.[a-z0-9-]+)*\\.securedrop\\.tor\\.onion\\/",
+ replaces_default_rulesets: false,
+};
+
+class HttpsEverywhereControl {
+ constructor() {
+ this._extensionMessaging = null;
+ }
+
+ async _sendMessage(type, object) {
+ return this._extensionMessaging.sendMessage(
+ {
+ type,
+ object,
+ },
+ EXTENSION_ID
+ );
+ }
+
+ static async wait(seconds = 1) {
+ return new Promise(resolve => setTimeout(resolve, seconds * 1000));
+ }
+
+ /**
+ * Installs the .tor.onion update channel in https-everywhere
+ */
+ async installTorOnionUpdateChannel(retries = 5) {
+ this._init();
+
+ // TODO: https-everywhere store is initialized asynchronously, so sending a message
+ // immediately results in a `store.get is undefined` error.
+ // For now, let's wait a bit and retry a few times if there is an error, but perhaps
+ // we could suggest https-everywhere to send a message when that happens and listen
+ // for that here.
+ await HttpsEverywhereControl.wait();
+
+ try {
+ // TODO: we may want a way to "lock" this update channel, so that it cannot be modified
+ // by the user via UI, but I think this is not possible at the time of writing via
+ // the existing messages in https-everywhere.
+ await this._sendMessage(
+ "create_update_channel",
+ SECUREDROP_TOR_ONION_CHANNEL.name
+ );
+ } catch (e) {
+ if (retries <= 0) {
+ throw new Error("Could not install SecureDropTorOnion update channel");
+ }
+ await this.installTorOnionUpdateChannel(retries - 1);
+ return;
+ }
+
+ await this._sendMessage(
+ "update_update_channel",
+ SECUREDROP_TOR_ONION_CHANNEL
+ );
+ }
+
+ /**
+ * Returns the .tor.onion rulesets available in https-everywhere
+ */
+ async getTorOnionRules() {
+ return this._sendMessage("get_simple_rules_ending_with", ".tor.onion");
+ }
+
+ /**
+ * Returns the timestamp of the last .tor.onion update channel update.
+ */
+ async getRulesetTimestamp() {
+ const rulesets = await this._sendMessage("get_ruleset_timestamps");
+ const securedrop =
+ rulesets &&
+ rulesets.find(([{ name }]) => name === SECUREDROP_TOR_ONION_CHANNEL.name);
+ if (securedrop) {
+ const [
+ updateChannel, // This has the same structure as SECUREDROP_TOR_ONION_CHANNEL
+ lastUpdatedTimestamp, // An integer, 0 if the update channel was never updated
+ ] = securedrop;
+ void updateChannel; // Ignore eslint unused warning for ruleset
+ return lastUpdatedTimestamp;
+ }
+ return null;
+ }
+
+ unload() {
+ if (this._extensionMessaging) {
+ this._extensionMessaging.unload();
+ this._extensionMessaging = null;
+ }
+ }
+
+ _init() {
+ if (!this._extensionMessaging) {
+ this._extensionMessaging = new ExtensionMessaging();
+ }
+ }
+}
diff --git a/browser/components/onionservices/OnionAliasStore.jsm b/browser/components/onionservices/OnionAliasStore.jsm
new file mode 100644
index 000000000000..66cf569227bf
--- /dev/null
+++ b/browser/components/onionservices/OnionAliasStore.jsm
@@ -0,0 +1,201 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["OnionAliasStore"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+const { setTimeout, clearTimeout } = ChromeUtils.import(
+ "resource://gre/modules/Timer.jsm"
+);
+const { HttpsEverywhereControl } = ChromeUtils.import(
+ "resource:///modules/HttpsEverywhereControl.jsm"
+);
+
+// Logger adapted from CustomizableUI.jsm
+const kPrefOnionAliasDebug = "browser.onionalias.debug";
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "gDebuggingEnabled",
+ kPrefOnionAliasDebug,
+ false,
+ (pref, oldVal, newVal) => {
+ if (typeof log != "undefined") {
+ log.maxLogLevel = newVal ? "all" : "log";
+ }
+ }
+);
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let scope = {};
+ ChromeUtils.import("resource://gre/modules/Console.jsm", scope);
+ let consoleOptions = {
+ maxLogLevel: gDebuggingEnabled ? "all" : "log",
+ prefix: "OnionAlias",
+ };
+ return new scope.ConsoleAPI(consoleOptions);
+});
+
+function observe(topic, callback) {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ if (topic === aTopic) {
+ callback(aSubject, aData);
+ }
+ },
+ };
+ Services.obs.addObserver(observer, topic);
+ return () => Services.obs.removeObserver(observer, topic);
+}
+
+class _OnionAliasStore {
+ static get RULESET_CHECK_INTERVAL() {
+ return 1000 * 60; // 1 minute
+ }
+
+ static get RULESET_CHECK_INTERVAL_FAST() {
+ return 1000 * 5; // 5 seconds
+ }
+
+ constructor() {
+ this._onionMap = new Map();
+ this._rulesetTimeout = null;
+ this._removeObserver = () => {};
+ this._canLoadRules = false;
+ this._rulesetTimestamp = null;
+ this._updateChannelInstalled = false;
+ }
+
+ async _periodicRulesetCheck() {
+ // TODO: it would probably be preferable to listen to some message broadcasted by
+ // the https-everywhere extension when some update channel is updated, instead of
+ // polling every N seconds.
+ log.debug("Checking for new rules");
+ const ts = await this.httpsEverywhereControl.getRulesetTimestamp();
+ log.debug(
+ `Found ruleset timestamp ${ts}, current is ${this._rulesetTimestamp}`
+ );
+ if (ts !== this._rulesetTimestamp) {
+ this._rulesetTimestamp = ts;
+ log.debug("New rules found, updating");
+ // We clear the mappings even if we cannot load the rules from https-everywhere,
+ // since we cannot be sure if the stored mappings are correct anymore.
+ this._clear();
+ if (this._canLoadRules) {
+ await this._loadRules();
+ }
+ }
+ // If the timestamp is 0, that means the update channel was not yet updated, so
+ // we schedule a check soon.
+ this._rulesetTimeout = setTimeout(
+ () => this._periodicRulesetCheck(),
+ ts === 0
+ ? _OnionAliasStore.RULESET_CHECK_INTERVAL_FAST
+ : _OnionAliasStore.RULESET_CHECK_INTERVAL
+ );
+ }
+
+ async init() {
+ this.httpsEverywhereControl = new HttpsEverywhereControl();
+
+ // Setup .tor.onion rule loading.
+ // The http observer is a fallback, and is removed in _loadRules() as soon as we are able
+ // to load some rules from HTTPS Everywhere.
+ this._loadHttpObserver();
+ try {
+ await this.httpsEverywhereControl.installTorOnionUpdateChannel();
+ this._updateChannelInstalled = true;
+ await this.httpsEverywhereControl.getTorOnionRules();
+ this._canLoadRules = true;
+ } catch (e) {
+ // Loading rules did not work, probably because "get_simple_rules_ending_with" is not yet
+ // working in https-everywhere. Use an http observer as a fallback for learning the rules.
+ log.debug(`Could not load rules: ${e.message}`);
+ }
+
+ // Setup checker for https-everywhere ruleset updates
+ if (this._updateChannelInstalled) {
+ this._periodicRulesetCheck();
+ }
+ }
+
+ /**
+ * Loads the .tor.onion mappings from https-everywhere.
+ */
+ async _loadRules() {
+ const rules = await this.httpsEverywhereControl.getTorOnionRules();
+ // Remove http observer if we are able to load some rules directly.
+ if (rules.length) {
+ this._removeObserver();
+ this._removeObserver = () => {};
+ }
+ this._clear();
+ log.debug(`Loading ${rules.length} rules`, rules);
+ for (const rule of rules) {
+ // Here we are trusting that the securedrop ruleset follows some conventions so that we can
+ // assume there is a host mapping from `rule.host` to the hostname of the URL in `rule.to`.
+ try {
+ const url = new URL(rule.to);
+ const shortHost = rule.host;
+ const longHost = url.hostname;
+ this._addMapping(shortHost, longHost);
+ } catch (e) {
+ log.error("Could not process rule:", rule);
+ }
+ }
+ }
+
+ /**
+ * Loads a http observer to listen for local redirects for populating
+ * the .tor.onion -> .onion mappings. Should only be used if we cannot ask https-everywhere
+ * directly for the mappings.
+ */
+ _loadHttpObserver() {
+ this._removeObserver = observe("http-on-before-connect", channel => {
+ if (
+ channel.isMainDocumentChannel &&
+ channel.originalURI.host.endsWith(".tor.onion")
+ ) {
+ this._addMapping(channel.originalURI.host, channel.URI.host);
+ }
+ });
+ }
+
+ uninit() {
+ this._clear();
+ this._removeObserver();
+ this._removeObserver = () => {};
+ if (this.httpsEverywhereControl) {
+ this.httpsEverywhereControl.unload();
+ delete this.httpsEverywhereControl;
+ }
+ clearTimeout(this._rulesetTimeout);
+ this._rulesetTimeout = null;
+ this._rulesetTimestamp = null;
+ }
+
+ _clear() {
+ this._onionMap.clear();
+ }
+
+ _addMapping(shortOnionHost, longOnionHost) {
+ this._onionMap.set(longOnionHost, shortOnionHost);
+ }
+
+ getShortURI(onionURI) {
+ if (
+ (onionURI.schemeIs("http") || onionURI.schemeIs("https")) &&
+ this._onionMap.has(onionURI.host)
+ ) {
+ return onionURI
+ .mutate()
+ .setHost(this._onionMap.get(onionURI.host))
+ .finalize();
+ }
+ return null;
+ }
+}
+
+let OnionAliasStore = new _OnionAliasStore();
diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build
index 2661ad7cb9f3..815685322024 100644
--- a/browser/components/onionservices/moz.build
+++ b/browser/components/onionservices/moz.build
@@ -1 +1,7 @@
JAR_MANIFESTS += ["jar.mn"]
+
+EXTRA_JS_MODULES += [
+ "ExtensionMessaging.jsm",
+ "HttpsEverywhereControl.jsm",
+ "OnionAliasStore.jsm",
+]
diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm
index f67483e1a905..bd32f806832b 100644
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -320,7 +320,10 @@ class UrlbarInput {
// bar if the user has deleted the URL and we'd just put the same URL
// back. See bug 304198.
if (value === null) {
- uri = uri || this.window.gBrowser.currentURI;
+ uri =
+ uri ||
+ this.window.gBrowser.selectedBrowser.currentOnionAliasURI ||
+ this.window.gBrowser.currentURI;
// Strip off usernames and passwords for the location bar
try {
uri = Services.io.createExposableURI(uri);
@@ -2096,7 +2099,13 @@ class UrlbarInput {
}
let uri;
- if (this.getAttribute("pageproxystate") == "valid") {
+ // When we rewrite .onion to an alias, gBrowser.currentURI will be different than
+ // the URI displayed in the urlbar. We need to use the urlbar value to copy the
+ // alias instead of the actual .onion URI that is loaded.
+ if (
+ this.getAttribute("pageproxystate") == "valid" &&
+ !this.window.gBrowser.selectedBrowser.currentOnionAliasURI
+ ) {
uri = this.window.gBrowser.currentURI;
} else {
// The value could be:
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
index e41c20cdba85..bc75f0bda019 100644
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5833,6 +5833,10 @@ void nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel,
return;
}
+ if (!mOnionUrlbarRewritesAllowed && IsTorOnionRedirect(oldURI, newURI)) {
+ mOnionUrlbarRewritesAllowed = true;
+ }
+
// DocumentChannel adds redirect chain to global history in the parent
// process. The redirect chain can't be queried from the content process, so
// there's no need to update global history here.
@@ -9147,6 +9151,20 @@ nsresult nsDocShell::HandleSameDocumentNavigation(
return NS_OK;
}
+/* static */
+bool nsDocShell::IsTorOnionRedirect(nsIURI* aOldURI, nsIURI* aNewURI) {
+ nsAutoCString oldHost;
+ nsAutoCString newHost;
+ if (aOldURI && aNewURI && NS_SUCCEEDED(aOldURI->GetHost(oldHost)) &&
+ StringEndsWith(oldHost, ".tor.onion"_ns) &&
+ NS_SUCCEEDED(aNewURI->GetHost(newHost)) &&
+ StringEndsWith(newHost, ".onion"_ns) &&
+ !StringEndsWith(newHost, ".tor.onion"_ns)) {
+ return true;
+ }
+ return false;
+}
+
nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
Maybe<uint32_t> aCacheKey) {
MOZ_ASSERT(aLoadState, "need a load state!");
@@ -9295,6 +9313,30 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
mAllowKeywordFixup =
aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP);
+
+ if (mOnionUrlbarRewritesAllowed) {
+ mOnionUrlbarRewritesAllowed = false;
+ nsCOMPtr<nsIURI> referrer;
+ nsIReferrerInfo* referrerInfo = aLoadState->GetReferrerInfo();
+ if (referrerInfo) {
+ referrerInfo->GetOriginalReferrer(getter_AddRefs(referrer));
+ bool isPrivateWin = false;
+ Document* doc = GetDocument();
+ if (doc) {
+ isPrivateWin =
+ doc->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId > 0;
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+ mOnionUrlbarRewritesAllowed =
+ secMan && NS_SUCCEEDED(secMan->CheckSameOriginURI(
+ aLoadState->URI(), referrer, false, isPrivateWin));
+ }
+ }
+ }
+ mOnionUrlbarRewritesAllowed =
+ mOnionUrlbarRewritesAllowed ||
+ aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES);
+
mURIResultedInDocument = false; // reset the clock...
// See if this is actually a load between two history entries for the same
@@ -11665,6 +11707,7 @@ nsresult nsDocShell::AddToSessionHistory(
HistoryID(), GetCreatedDynamically(), originalURI,
resultPrincipalURI, loadReplace, referrerInfo, srcdoc,
srcdocEntry, baseURI, saveLayoutState, expired);
+ entry->SetOnionUrlbarRewritesAllowed(mOnionUrlbarRewritesAllowed);
if (mBrowsingContext->IsTop() && GetSessionHistory()) {
bool shouldPersist = ShouldAddToSessionHistory(aURI, aChannel);
@@ -13432,3 +13475,12 @@ void nsDocShell::MoveLoadingToActiveEntry() {
}
}
}
+
+NS_IMETHODIMP
+nsDocShell::GetOnionUrlbarRewritesAllowed(bool* aOnionUrlbarRewritesAllowed) {
+ NS_ENSURE_ARG(aOnionUrlbarRewritesAllowed);
+ *aOnionUrlbarRewritesAllowed =
+ StaticPrefs::browser_urlbar_onionRewrites_enabled() &&
+ mOnionUrlbarRewritesAllowed;
+ return NS_OK;
+}
diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
index 780ea98730bb..8c09c6a6f467 100644
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -132,6 +132,9 @@ class nsDocShell final : public nsDocLoader,
// Whether the load should go through LoadURIDelegate.
INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE = 0x2000,
+
+ // Whether rewriting the urlbar to a short .onion alias is allowed.
+ INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES = 0x4000,
};
// Event type dispatched by RestorePresentation
@@ -555,6 +558,8 @@ class nsDocShell final : public nsDocLoader,
virtual void DestroyChildren() override;
+ static bool IsTorOnionRedirect(nsIURI* aOldURI, nsIURI* aNewURI);
+
// Overridden from nsDocLoader, this provides more information than the
// normal OnStateChange with flags STATE_REDIRECTING
virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
@@ -1211,6 +1216,7 @@ class nsDocShell final : public nsDocLoader,
bool mCSSErrorReportingEnabled : 1;
bool mAllowAuth : 1;
bool mAllowKeywordFixup : 1;
+ bool mOnionUrlbarRewritesAllowed : 1;
bool mIsOffScreenBrowser : 1;
bool mDisableMetaRefreshWhenInactive : 1;
bool mIsAppTab : 1;
diff --git a/docshell/base/nsDocShellLoadState.cpp b/docshell/base/nsDocShellLoadState.cpp
index 8d9a329eeedf..36b27876d487 100644
--- a/docshell/base/nsDocShellLoadState.cpp
+++ b/docshell/base/nsDocShellLoadState.cpp
@@ -764,6 +764,10 @@ void nsDocShellLoadState::CalculateLoadURIFlags() {
mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
}
+ if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES) {
+ mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES;
+ }
+
if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD) {
mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FIRST_LOAD;
}
diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl
index afa1eee3a610..966f48c0993f 100644
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -897,4 +897,9 @@ interface nsIDocShell : nsIDocShellTreeItem
* until session history state is moved into the parent process.
*/
void persistLayoutHistoryState();
+
+ /**
+ * Whether rewriting the urlbar to a short .onion alias is allowed.
+ */
+ [infallible] readonly attribute boolean onionUrlbarRewritesAllowed;
};
diff --git a/docshell/base/nsIWebNavigation.idl b/docshell/base/nsIWebNavigation.idl
index 30b6dd276ce0..8f45b6fa79bd 100644
--- a/docshell/base/nsIWebNavigation.idl
+++ b/docshell/base/nsIWebNavigation.idl
@@ -257,6 +257,11 @@ interface nsIWebNavigation : nsISupports
*/
const unsigned long LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE = 0x4000000;
+ /**
+ * Allow rewriting the urlbar to a short .onion alias.
+ */
+ const unsigned long LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES = 0x8000000;
+
/**
* Loads a given URI. This will give priority to loading the requested URI
* in the object implementing this interface. If it can't be loaded here
diff --git a/docshell/shistory/SessionHistoryEntry.cpp b/docshell/shistory/SessionHistoryEntry.cpp
index ad7d25e8b70a..61e2a1108359 100644
--- a/docshell/shistory/SessionHistoryEntry.cpp
+++ b/docshell/shistory/SessionHistoryEntry.cpp
@@ -894,6 +894,20 @@ SessionHistoryEntry::SetPersist(bool aPersist) {
return NS_OK;
}
+NS_IMETHODIMP
+SessionHistoryEntry::GetOnionUrlbarRewritesAllowed(
+ bool* aOnionUrlbarRewritesAllowed) {
+ *aOnionUrlbarRewritesAllowed = mInfo->mOnionUrlbarRewritesAllowed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetOnionUrlbarRewritesAllowed(
+ bool aOnionUrlbarRewritesAllowed) {
+ mInfo->mOnionUrlbarRewritesAllowed = aOnionUrlbarRewritesAllowed;
+ return NS_OK;
+}
+
NS_IMETHODIMP
SessionHistoryEntry::GetScrollPosition(int32_t* aX, int32_t* aY) {
*aX = mInfo->mScrollPositionX;
diff --git a/docshell/shistory/SessionHistoryEntry.h b/docshell/shistory/SessionHistoryEntry.h
index 655eb6294fd2..b2c2957c3f26 100644
--- a/docshell/shistory/SessionHistoryEntry.h
+++ b/docshell/shistory/SessionHistoryEntry.h
@@ -158,6 +158,7 @@ class SessionHistoryInfo {
bool mScrollRestorationIsManual = false;
bool mPersist = true;
bool mHasUserInteraction = false;
+ bool mOnionUrlbarRewritesAllowed = false;
union SharedState {
SharedState();
diff --git a/docshell/shistory/nsISHEntry.idl b/docshell/shistory/nsISHEntry.idl
index af5b3f4b4a89..706158424394 100644
--- a/docshell/shistory/nsISHEntry.idl
+++ b/docshell/shistory/nsISHEntry.idl
@@ -252,6 +252,11 @@ interface nsISHEntry : nsISupports
*/
[infallible] attribute boolean persist;
+ /**
+ * Whether rewriting the urlbar to a short .onion alias is allowed.
+ */
+ [infallible] attribute boolean onionUrlbarRewritesAllowed;
+
/**
* Set/Get the visual viewport scroll position if session history is
* changed through anchor navigation or pushState.
diff --git a/docshell/shistory/nsSHEntry.cpp b/docshell/shistory/nsSHEntry.cpp
index 8258582e734f..3b4ac141d7ab 100644
--- a/docshell/shistory/nsSHEntry.cpp
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -43,7 +43,8 @@ nsSHEntry::nsSHEntry()
mScrollRestorationIsManual(false),
mLoadedInThisProcess(false),
mPersist(true),
- mHasUserInteraction(false) {}
+ mHasUserInteraction(false),
+ mOnionUrlbarRewritesAllowed(false) {}
nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
: mShared(aOther.mShared),
@@ -70,7 +71,8 @@ nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
mScrollRestorationIsManual(false),
mLoadedInThisProcess(aOther.mLoadedInThisProcess),
mPersist(aOther.mPersist),
- mHasUserInteraction(false) {}
+ mHasUserInteraction(false),
+ mOnionUrlbarRewritesAllowed(aOther.mOnionUrlbarRewritesAllowed) {}
nsSHEntry::~nsSHEntry() {
// Null out the mParent pointers on all our kids.
@@ -864,6 +866,18 @@ nsSHEntry::SetPersist(bool aPersist) {
return NS_OK;
}
+NS_IMETHODIMP
+nsSHEntry::GetOnionUrlbarRewritesAllowed(bool* aOnionUrlbarRewritesAllowed) {
+ *aOnionUrlbarRewritesAllowed = mOnionUrlbarRewritesAllowed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetOnionUrlbarRewritesAllowed(bool aOnionUrlbarRewritesAllowed) {
+ mOnionUrlbarRewritesAllowed = aOnionUrlbarRewritesAllowed;
+ return NS_OK;
+}
+
NS_IMETHODIMP
nsSHEntry::CreateLoadInfo(nsDocShellLoadState** aLoadState) {
nsCOMPtr<nsIURI> uri = GetURI();
@@ -913,6 +927,10 @@ nsSHEntry::CreateLoadInfo(nsDocShellLoadState** aLoadState) {
} else {
srcdoc = VoidString();
}
+ if (GetOnionUrlbarRewritesAllowed()) {
+ flags |= nsDocShell::InternalLoad::
+ INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES;
+ }
loadState->SetSrcdocData(srcdoc);
loadState->SetBaseURI(baseURI);
loadState->SetLoadFlags(flags);
diff --git a/docshell/shistory/nsSHEntry.h b/docshell/shistory/nsSHEntry.h
index 20bb96541583..0bc6982db883 100644
--- a/docshell/shistory/nsSHEntry.h
+++ b/docshell/shistory/nsSHEntry.h
@@ -65,6 +65,7 @@ class nsSHEntry : public nsISHEntry {
bool mLoadedInThisProcess;
bool mPersist;
bool mHasUserInteraction;
+ bool mOnionUrlbarRewritesAllowed;
};
#endif /* nsSHEntry_h */
diff --git a/dom/interfaces/base/nsIBrowser.idl b/dom/interfaces/base/nsIBrowser.idl
index d6df6411e97a..868b9675a3c4 100644
--- a/dom/interfaces/base/nsIBrowser.idl
+++ b/dom/interfaces/base/nsIBrowser.idl
@@ -131,7 +131,8 @@ interface nsIBrowser : nsISupports
in boolean aIsSynthetic,
in boolean aHasRequestContextID,
in uint64_t aRequestContextID,
- in AString aContentType);
+ in AString aContentType,
+ in boolean aOnionUrlbarRewritesAllowed);
/**
* Determine what process switching behavior this browser element should have.
diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp
index 0c56a907d71f..b3eedccbf9c9 100644
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -3659,6 +3659,8 @@ NS_IMETHODIMP BrowserChild::OnLocationChange(nsIWebProgress* aWebProgress,
docShell->GetMayEnableCharacterEncodingMenu();
locationChangeData->charsetAutodetected() =
docShell->GetCharsetAutodetected();
+ locationChangeData->onionUrlbarRewritesAllowed() =
+ docShell->GetOnionUrlbarRewritesAllowed();
locationChangeData->contentPrincipal() = document->NodePrincipal();
locationChangeData->contentPartitionedPrincipal() =
diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp
index 2077a5e0943d..87e02ac48ad4 100644
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -2679,7 +2679,8 @@ mozilla::ipc::IPCResult BrowserParent::RecvOnLocationChange(
aLocationChangeData->isSyntheticDocument(),
aLocationChangeData->requestContextID().isSome(),
aLocationChangeData->requestContextID().valueOr(0),
- aLocationChangeData->contentType());
+ aLocationChangeData->contentType(),
+ aLocationChangeData->onionUrlbarRewritesAllowed());
}
GetBrowsingContext()->Top()->GetWebProgress()->OnLocationChange(
diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl
index 798e1e7e477f..327c3efb9e25 100644
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -146,6 +146,7 @@ struct WebProgressLocationChangeData
bool isSyntheticDocument;
bool mayEnableCharacterEncodingMenu;
bool charsetAutodetected;
+ bool onionUrlbarRewritesAllowed;
nsString contentType;
nsString title;
nsString charset;
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
index ad8816ce4294..d6a3ab246838 100644
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -1155,6 +1155,12 @@
value: true
mirror: always
+ # Whether rewriting the urlbar to a short .onion alias is allowed.
+- name: browser.urlbar.onionRewrites.enabled
+ type: RelaxedAtomicBool
+ value: true
+ mirror: always
+
- name: browser.viewport.desktopWidth
type: RelaxedAtomicInt32
value: 980
diff --git a/netwerk/dns/effective_tld_names.dat b/netwerk/dns/effective_tld_names.dat
index 9236a929192e..8d7955f557d0 100644
--- a/netwerk/dns/effective_tld_names.dat
+++ b/netwerk/dns/effective_tld_names.dat
@@ -5517,6 +5517,8 @@ pro.om
// onion : https://tools.ietf.org/html/rfc7686
onion
+tor.onion
+securedrop.tor.onion
// org : https://en.wikipedia.org/wiki/.org
org
diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp
index 3d7611ba7a55..c4ca43d28a2e 100644
--- a/netwerk/ipc/DocumentLoadListener.cpp
+++ b/netwerk/ipc/DocumentLoadListener.cpp
@@ -2428,6 +2428,16 @@ DocumentLoadListener::AsyncOnChannelRedirect(
mLoadStateLoadType, nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT));
}
+ // Like the code above for allowing mixed content, we need to check this here
+ // in case the redirect is not handled in the docshell.
+ nsCOMPtr<nsIURI> oldURI, newURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+ if (nsDocShell::IsTorOnionRedirect(oldURI, newURI)) {
+ mLoadStateLoadFlags |=
+ nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES;
+ }
+
// We need the original URI of the current channel to use to open the real
// channel in the content process. Unfortunately we overwrite the original
// uri of the new channel with the original pre-redirect URI, so grab
diff --git a/toolkit/content/widgets/browser-custom-element.js b/toolkit/content/widgets/browser-custom-element.js
index 98aa12a2e190..23f2f1efdfc5 100644
--- a/toolkit/content/widgets/browser-custom-element.js
+++ b/toolkit/content/widgets/browser-custom-element.js
@@ -220,6 +220,8 @@
this._mayEnableCharacterEncodingMenu = null;
+ this._onionUrlbarRewritesAllowed = false;
+
this._charsetAutodetected = false;
this._contentPrincipal = null;
@@ -580,6 +582,12 @@
}
}
+ get onionUrlbarRewritesAllowed() {
+ return this.isRemoteBrowser
+ ? this._onionUrlbarRewritesAllowed
+ : this.docShell.onionUrlbarRewritesAllowed;
+ }
+
get charsetAutodetected() {
return this.isRemoteBrowser
? this._charsetAutodetected
@@ -1124,7 +1132,8 @@
aIsSynthetic,
aHaveRequestContextID,
aRequestContextID,
- aContentType
+ aContentType,
+ aOnionUrlbarRewritesAllowed
) {
if (this.isRemoteBrowser && this.messageManager) {
if (aCharset != null) {
@@ -1147,6 +1156,7 @@
this._contentRequestContextID = aHaveRequestContextID
? aRequestContextID
: null;
+ this._onionUrlbarRewritesAllowed = aOnionUrlbarRewritesAllowed;
}
}
@@ -1563,6 +1573,7 @@
"_contentPrincipal",
"_contentPartitionedPrincipal",
"_isSyntheticDocument",
+ "_onionUrlbarRewritesAllowed",
]
);
}
diff --git a/toolkit/modules/sessionstore/SessionHistory.jsm b/toolkit/modules/sessionstore/SessionHistory.jsm
index aeeb62d4c4be..f529e2148298 100644
--- a/toolkit/modules/sessionstore/SessionHistory.jsm
+++ b/toolkit/modules/sessionstore/SessionHistory.jsm
@@ -326,6 +326,7 @@ var SessionHistoryInternal = {
}
entry.persist = shEntry.persist;
+ entry.onionUrlbarRewritesAllowed = shEntry.onionUrlbarRewritesAllowed;
return entry;
},
@@ -620,6 +621,10 @@ var SessionHistoryInternal = {
}
}
+ if (entry.onionUrlbarRewritesAllowed) {
+ shEntry.onionUrlbarRewritesAllowed = entry.onionUrlbarRewritesAllowed;
+ }
+
return shEntry;
},
diff --git a/xpcom/reflect/xptinfo/xptinfo.h b/xpcom/reflect/xptinfo/xptinfo.h
index 33b1f25411fd..e8a9d9d9c592 100644
--- a/xpcom/reflect/xptinfo/xptinfo.h
+++ b/xpcom/reflect/xptinfo/xptinfo.h
@@ -513,7 +513,8 @@ static_assert(sizeof(nsXPTMethodInfo) == 8, "wrong size");
#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
# define PARAM_BUFFER_COUNT 18
#else
-# define PARAM_BUFFER_COUNT 14
+// The max is currently updateForLocationChange in nsIBrowser.idl
+# define PARAM_BUFFER_COUNT 15
#endif
/**
More information about the tor-commits
mailing list