tor-commits
Threads by month
- ----- 2025 -----
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
September 2021
- 15 participants
- 1055 discussions

[tor-browser/tor-browser-78.14.0esr-10.5-1] Orfox: Centralized proxy applied to AbstractCommunicator and BaseResources.
by sysrqb@torproject.org 04 Sep '21
by sysrqb@torproject.org 04 Sep '21
04 Sep '21
commit a9572dfee5e413228daf01e0525a899482d12455
Author: Amogh Pradeep <amoghbl1(a)gmail.com>
Date: Fri Jun 12 02:07:45 2015 -0400
Orfox: Centralized proxy applied to AbstractCommunicator and BaseResources.
See Bug 1357997 for partial uplift.
Also:
Bug 28051 - Use our Orbot for proxying our connections
Bug 31144 - ESR68 Network Code Review
---
.../main/java/org/mozilla/gecko/GeckoAppShell.java | 68 +++++++++++-----------
.../java/org/mozilla/gecko/util/BitmapUtils.java | 7 ---
.../java/org/mozilla/gecko/util/ProxySelector.java | 25 +++++++-
3 files changed, 59 insertions(+), 41 deletions(-)
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
index 995b23316c32..b9ca73bee2eb 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -1764,39 +1764,41 @@ public class GeckoAppShell {
@WrapForJNI
private static URLConnection getConnection(final String url) {
- try {
- String spec;
- if (url.startsWith("android://")) {
- spec = url.substring(10);
- } else {
- spec = url.substring(8);
- }
-
- // Check if we are loading a package icon.
- try {
- if (spec.startsWith("icon/")) {
- String[] splits = spec.split("/");
- if (splits.length != 2) {
- return null;
- }
- final String pkg = splits[1];
- final PackageManager pm = getApplicationContext().getPackageManager();
- final Drawable d = pm.getApplicationIcon(pkg);
- final Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(d);
- return new BitmapConnection(bitmap);
- }
- } catch (Exception ex) {
- Log.e(LOGTAG, "error", ex);
- }
-
- // if the colon got stripped, put it back
- int colon = spec.indexOf(':');
- if (colon == -1 || colon > spec.indexOf('/')) {
- spec = spec.replaceFirst("/", ":/");
- }
- } catch (Exception ex) {
- return null;
- }
+ // Bug 31144 - Prevent potential proxy-bypass
+
+ //try {
+ // String spec;
+ // if (url.startsWith("android://")) {
+ // spec = url.substring(10);
+ // } else {
+ // spec = url.substring(8);
+ // }
+
+ // // Check if we are loading a package icon.
+ // try {
+ // if (spec.startsWith("icon/")) {
+ // String[] splits = spec.split("/");
+ // if (splits.length != 2) {
+ // return null;
+ // }
+ // final String pkg = splits[1];
+ // final PackageManager pm = getApplicationContext().getPackageManager();
+ // final Drawable d = pm.getApplicationIcon(pkg);
+ // final Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(d);
+ // return new BitmapConnection(bitmap);
+ // }
+ // } catch (Exception ex) {
+ // Log.e(LOGTAG, "error", ex);
+ // }
+
+ // // if the colon got stripped, put it back
+ // int colon = spec.indexOf(':');
+ // if (colon == -1 || colon > spec.indexOf('/')) {
+ // spec = spec.replaceFirst("/", ":/");
+ // }
+ //} catch (Exception ex) {
+ // return null;
+ //}
return null;
}
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BitmapUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BitmapUtils.java
index 73a69a3abd66..f795dacffb47 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BitmapUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BitmapUtils.java
@@ -101,13 +101,6 @@ public final class BitmapUtils {
public static Bitmap decodeUrl(final URL url) {
InputStream stream = null;
- try {
- stream = url.openStream();
- } catch (IOException e) {
- Log.w(LOGTAG, "decodeUrl: IOException downloading " + url);
- return null;
- }
-
if (stream == null) {
Log.w(LOGTAG, "decodeUrl: stream not found downloading " + url);
return null;
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java
index 3940d3c84249..9515975f680a 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java
@@ -29,6 +29,10 @@ import java.net.URLConnection;
import java.util.List;
public class ProxySelector {
+ private static final String TOR_PROXY_ADDRESS = "127.0.0.1";
+ private static final int TOR_SOCKS_PROXY_PORT = 9150;
+ private static final int TOR_HTTP_PROXY_PORT = 8218;
+
public static URLConnection openConnectionWithProxy(final URI uri) throws IOException {
java.net.ProxySelector ps = java.net.ProxySelector.getDefault();
Proxy proxy = Proxy.NO_PROXY;
@@ -39,7 +43,26 @@ public class ProxySelector {
}
}
- return uri.toURL().openConnection(proxy);
+ /* Ignore the proxy we found from the VM, only use Tor. We can probably
+ * safely use the logic in this class in the future. */
+ return uri.toURL().openConnection(getProxy());
+ }
+
+ public static Proxy getProxy() {
+ // TODO make configurable
+ return new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(TOR_PROXY_ADDRESS, TOR_SOCKS_PROXY_PORT));
+ }
+
+ public static String getProxyHostAddress() {
+ return TOR_PROXY_ADDRESS;
+ }
+
+ public static int getSocksProxyPort() {
+ return TOR_SOCKS_PROXY_PORT;
+ }
+
+ public static int getHttpProxyPort() {
+ return TOR_HTTP_PROXY_PORT;
}
public ProxySelector() {
1
0

[tor-browser/tor-browser-78.14.0esr-10.5-1] Bug 28125 - Prevent non-Necko network connections
by sysrqb@torproject.org 04 Sep '21
by sysrqb@torproject.org 04 Sep '21
04 Sep '21
commit 283916a9d8748bb2cfb768ec0037e9f9c606a4a9
Author: Matthew Finkel <Matthew.Finkel(a)gmail.com>
Date: Thu Oct 25 19:17:09 2018 +0000
Bug 28125 - Prevent non-Necko network connections
---
.../gecko/media/GeckoMediaDrmBridgeV21.java | 49 +---------------------
.../exoplayer2/upstream/DefaultHttpDataSource.java | 47 +--------------------
2 files changed, 3 insertions(+), 93 deletions(-)
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
index 3ba59bfd6776..eb57b1013642 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
@@ -488,54 +488,7 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
@Override
protected Void doInBackground(final Void... params) {
- HttpURLConnection urlConnection = null;
- BufferedReader in = null;
- try {
- URI finalURI = new URI(mURL + "&signedRequest=" + URLEncoder.encode(new String(mDrmRequest), "UTF-8"));
- urlConnection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(finalURI);
- urlConnection.setRequestMethod("POST");
- if (DEBUG) Log.d(LOGTAG, "Provisioning, posting url =" + finalURI.toString());
-
- // Add data
- urlConnection.setRequestProperty("Accept", "*/*");
- urlConnection.setRequestProperty("User-Agent", getCDMUserAgent());
- urlConnection.setRequestProperty("Content-Type", "application/json");
-
- // Execute HTTP Post Request
- urlConnection.connect();
-
- int responseCode = urlConnection.getResponseCode();
- if (responseCode == HttpURLConnection.HTTP_OK) {
- in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StringUtils.UTF_8));
- String inputLine;
- StringBuffer response = new StringBuffer();
-
- while ((inputLine = in.readLine()) != null) {
- response.append(inputLine);
- }
- in.close();
- mResponseBody = String.valueOf(response).getBytes(StringUtils.UTF_8);
- if (DEBUG) Log.d(LOGTAG, "Provisioning, response received.");
- if (mResponseBody != null) Log.d(LOGTAG, "response length=" + mResponseBody.length);
- } else {
- Log.d(LOGTAG, "Provisioning, server returned HTTP error code :" + responseCode);
- }
- } catch (IOException e) {
- Log.e(LOGTAG, "Got exception during posting provisioning request ...", e);
- } catch (URISyntaxException e) {
- Log.e(LOGTAG, "Got exception during creating uri ...", e);
- } finally {
- if (urlConnection != null) {
- urlConnection.disconnect();
- }
- try {
- if (in != null) {
- in.close();
- }
- } catch (IOException e) {
- Log.e(LOGTAG, "Exception during closing in ...", e);
- }
- }
+ Log.i(LOGTAG, "This is Tor Browser. Skipping.");
return null;
}
diff --git a/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java
index fdb44cc2ea1f..7a2044721d2d 100644
--- a/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java
+++ b/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java
@@ -395,51 +395,8 @@ public class DefaultHttpDataSource implements HttpDataSource {
*/
private HttpURLConnection makeConnection(URL url, byte[] postBody, long position,
long length, boolean allowGzip, boolean followRedirects) throws IOException, URISyntaxException {
- /**
- * Tor Project modified the way the connection object was created. For the sake of
- * simplicity, instead of duplicating the whole file we changed the connection object
- * to use the ProxySelector.
- */
- HttpURLConnection connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(url.toURI());
-
- connection.setConnectTimeout(connectTimeoutMillis);
- connection.setReadTimeout(readTimeoutMillis);
- if (defaultRequestProperties != null) {
- for (Map.Entry<String, String> property : defaultRequestProperties.getSnapshot().entrySet()) {
- connection.setRequestProperty(property.getKey(), property.getValue());
- }
- }
- for (Map.Entry<String, String> property : requestProperties.getSnapshot().entrySet()) {
- connection.setRequestProperty(property.getKey(), property.getValue());
- }
- if (!(position == 0 && length == C.LENGTH_UNSET)) {
- String rangeRequest = "bytes=" + position + "-";
- if (length != C.LENGTH_UNSET) {
- rangeRequest += (position + length - 1);
- }
- connection.setRequestProperty("Range", rangeRequest);
- }
- connection.setRequestProperty("User-Agent", userAgent);
- if (!allowGzip) {
- connection.setRequestProperty("Accept-Encoding", "identity");
- }
- connection.setInstanceFollowRedirects(followRedirects);
- connection.setDoOutput(postBody != null);
- if (postBody != null) {
- connection.setRequestMethod("POST");
- if (postBody.length == 0) {
- connection.connect();
- } else {
- connection.setFixedLengthStreamingMode(postBody.length);
- connection.connect();
- OutputStream os = connection.getOutputStream();
- os.write(postBody);
- os.close();
- }
- } else {
- connection.connect();
- }
- return connection;
+ Log.i(TAG, "This is Tor Browser. Skipping.");
+ throw new IOException();
}
/**
1
0

[tor-browser/tor-browser-78.14.0esr-10.5-1] Bug 25741 - TBA: Disable GeckoNetworkManager
by sysrqb@torproject.org 04 Sep '21
by sysrqb@torproject.org 04 Sep '21
04 Sep '21
commit 2aaa22b982e946257b7f4610719403bb7b591629
Author: Matthew Finkel <Matthew.Finkel(a)gmail.com>
Date: Thu Apr 26 22:22:51 2018 +0000
Bug 25741 - TBA: Disable GeckoNetworkManager
The browser should not need information related to the network
interface or network state, tor should take care of that.
---
.../src/main/java/org/mozilla/geckoview/GeckoRuntime.java | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
index ed86dcc5c299..f5587dd7e59c 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
@@ -121,7 +121,9 @@ public final class GeckoRuntime implements Parcelable {
mPaused = false;
// Monitor network status and send change notifications to Gecko
// while active.
- GeckoNetworkManager.getInstance().start(GeckoAppShell.getApplicationContext());
+ if (BuildConfig.TOR_BROWSER_VERSION == "") {
+ GeckoNetworkManager.getInstance().start(GeckoAppShell.getApplicationContext());
+ }
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
@@ -129,7 +131,9 @@ public final class GeckoRuntime implements Parcelable {
Log.d(LOGTAG, "Lifecycle: onPause");
mPaused = true;
// Stop monitoring network status while inactive.
- GeckoNetworkManager.getInstance().stop();
+ if (BuildConfig.TOR_BROWSER_VERSION == "") {
+ GeckoNetworkManager.getInstance().stop();
+ }
GeckoThread.onPause();
}
}
1
0

[tor-browser/tor-browser-78.14.0esr-10.5-1] Bug 28005: Implement .onion alias urlbar rewrites
by sysrqb@torproject.org 04 Sep '21
by sysrqb@torproject.org 04 Sep '21
04 Sep '21
commit e805affcc12bcef9cdbdb5fdd90cccc530b8217f
Author: Alex Catarineu <acat(a)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
Bug 40456: Update the SecureDrop HTTPS-Everywhere update channel
Bug 40478: Onion alias url rewrite is broken
---
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 | 86 +++++++++
.../onionservices/HttpsEverywhereControl.jsm | 147 +++++++++++++++
.../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 +
37 files changed, 757 insertions(+), 22 deletions(-)
diff --git a/browser/actors/ClickHandlerChild.jsm b/browser/actors/ClickHandlerChild.jsm
index 7dd060e83061..206a8fc97a4d 100644
--- a/browser/actors/ClickHandlerChild.jsm
+++ b/browser/actors/ClickHandlerChild.jsm
@@ -138,6 +138,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 454c0fe69b27..42ab7a0f6e2a 100644
--- a/browser/actors/ClickHandlerParent.jsm
+++ b/browser/actors/ClickHandlerParent.jsm
@@ -102,6 +102,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 16a3f8ecbc81..1a1b38aa6375 100644
--- a/browser/actors/ContextMenuChild.jsm
+++ b/browser/actors/ContextMenuChild.jsm
@@ -575,6 +575,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
@@ -687,6 +690,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 d4b71f87da04..9a9bb3e0dad4 100644
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -452,7 +452,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;
@@ -556,7 +557,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)) {
@@ -1655,14 +1656,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 b7fa432d9478..539d6d4056a3 100644
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -474,13 +474,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;
@@ -566,17 +566,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;
}
},
@@ -1000,8 +1001,9 @@ var gIdentityHandler = {
this.updateSitePermissions();
},
- setURI(uri) {
+ setURI(uri, onionAliasURI) {
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 9f5f0c608536..bd5f10cb6f64 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -77,6 +77,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",
@@ -2325,6 +2326,7 @@ var gBrowserInit = {
// [9]: allowInheritPrincipal (bool)
// [10]: csp (nsIContentSecurityPolicy)
// [11]: nsOpenWindowInfo
+ // [12]: onionUrlbarRewritesAllowed (bool)
let userContextId =
window.arguments[5] != undefined
? window.arguments[5]
@@ -2344,7 +2346,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 {
@@ -3230,7 +3233,8 @@ function loadURI(
forceAboutBlankViewerInCurrent,
triggeringPrincipal,
allowInheritPrincipal = false,
- csp = null
+ csp = null,
+ onionUrlbarRewritesAllowed = false
) {
if (!triggeringPrincipal) {
throw new Error("Must load with a triggering Principal");
@@ -3248,6 +3252,7 @@ function loadURI(
csp,
forceAboutBlankViewerInCurrent,
allowInheritPrincipal,
+ onionUrlbarRewritesAllowed,
});
} catch (e) {
Cu.reportError(e);
@@ -5359,11 +5364,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();
@@ -5534,6 +5552,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;
if (this._state == aState && this._lastLocation == spec) {
// Switching to a tab of the same URL doesn't change most security
@@ -5551,7 +5570,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
@@ -7011,6 +7030,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 frameOuterWindowID = WebNavigationFrames.getFrameId(doc.defaultView);
urlSecurityCheck(href, doc.nodePrincipal);
@@ -7023,6 +7057,7 @@ function handleLinkClick(event, href, linkNode) {
triggeringPrincipal: doc.nodePrincipal,
csp: doc.csp,
frameOuterWindowID,
+ 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 354b504ce0b4..ca351c71f1cb 100644
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -57,6 +57,7 @@ function openContextMenu(aMessage, aBrowser, aActor) {
disableSetDesktopBackground: data.disableSetDesktopBackground,
loginFillInfo: data.loginFillInfo,
parentAllowsMixedContent: data.parentAllowsMixedContent,
+ parentAllowsOnionUrlbarRewrites: data.parentAllowsOnionUrlbarRewrites,
userContextId: data.userContextId,
webExtContextData: data.webExtContextData,
};
@@ -1059,6 +1060,7 @@ class nsContextMenu {
triggeringPrincipal: this.principal,
csp: this.csp,
frameOuterWindowID: this.contentData.frameOuterWindowID,
+ onionUrlbarRewritesAllowed: false,
};
for (let p in extra) {
params[p] = extra[p];
@@ -1082,6 +1084,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 664dace84d9f..cd03cea3b6d9 100644
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -390,7 +390,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 4331ebc4b219..29395e96ce57 100644
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -250,7 +250,7 @@ var security = {
},
};
-async function securityOnLoad(uri, windowInfo) {
+async function securityOnLoad(uri, windowInfo, onionAliasURI) {
await security.init(uri, windowInfo);
let info = security.securityInfo;
@@ -263,6 +263,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 8ce4a2710cb8..8c313eaaffcb 100644
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -1547,6 +1547,7 @@
var aRelatedToCurrent;
var aAllowInheritPrincipal;
var aAllowMixedContent;
+ var aOnionUrlbarRewritesAllowed;
var aSkipAnimation;
var aForceNotRemote;
var aPreferredRemoteType;
@@ -1577,6 +1578,7 @@
aRelatedToCurrent = params.relatedToCurrent;
aAllowInheritPrincipal = !!params.allowInheritPrincipal;
aAllowMixedContent = params.allowMixedContent;
+ aOnionUrlbarRewritesAllowed = params.onionUrlbarRewritesAllowed;
aSkipAnimation = params.skipAnimation;
aForceNotRemote = params.forceNotRemote;
aPreferredRemoteType = params.preferredRemoteType;
@@ -1618,6 +1620,7 @@
relatedToCurrent: aRelatedToCurrent,
skipAnimation: aSkipAnimation,
allowMixedContent: aAllowMixedContent,
+ onionUrlbarRewritesAllowed: aOnionUrlbarRewritesAllowed,
forceNotRemote: aForceNotRemote,
createLazyBrowser: aCreateLazyBrowser,
preferredRemoteType: aPreferredRemoteType,
@@ -2504,6 +2507,7 @@
{
allowInheritPrincipal,
allowMixedContent,
+ onionUrlbarRewritesAllowed,
allowThirdPartyFixup,
bulkOrderedOpen,
charset,
@@ -2833,6 +2837,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 a23d6f05e6a7..eb13d5a3435c 100644
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -367,6 +367,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;
@@ -482,6 +483,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);
@@ -498,6 +504,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;
@@ -614,6 +622,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 (
@@ -661,6 +672,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 7d147d01d561..d30abff54562 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -703,6 +703,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
TabUnloader: "resource:///modules/TabUnloader.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",
@@ -2046,6 +2047,7 @@ BrowserGlue.prototype = {
Normandy.uninit();
RFPHelper.uninit();
+ OnionAliasStore.uninit();
},
// Set up a listener to enable/disable the screenshots extension
@@ -2412,6 +2414,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..b5d69df93807
--- /dev/null
+++ b/browser/components/onionservices/ExtensionMessaging.jsm
@@ -0,0 +1,86 @@
+// 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"
+);
+
+class ExtensionMessaging {
+ constructor() {
+ this._callback = null;
+ this._handlers = new Map();
+ this._messageManager = Services.cpmm;
+ }
+
+ async sendMessage(msg, extensionId) {
+ this._init();
+
+ const addon = await AddonManager.getAddonByID(extensionId);
+ if (!addon) {
+ throw new Error(`extension '${extensionId} does not exist`);
+ }
+ await addon.startupPromise;
+
+ const channelId = ExtensionUtils.getUniqueId();
+ return new Promise((resolve, reject) => {
+ this._handlers.set(channelId, { resolve, reject });
+ this._messageManager.sendAsyncMessage("MessageChannel:Messages", [
+ {
+ messageName: "Extension:Message",
+ sender: {
+ id: extensionId,
+ extensionId,
+ },
+ recipient: { extensionId },
+ data: new StructuredCloneHolder(msg),
+ channelId,
+ responseType: MessageChannel.RESPONSE_FIRST,
+ },
+ ]);
+ });
+ }
+
+ 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..525ed5233be7
--- /dev/null
+++ b/browser/components/onionservices/HttpsEverywhereControl.jsm
@@ -0,0 +1,147 @@
+// 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(a)eff.org";
+const SECUREDROP_TOR_ONION_CHANNEL_2020 = {
+ 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,
+};
+
+const SECUREDROP_TOR_ONION_CHANNEL = {
+ name: "SecureDropTorOnion2021",
+ jwk: {
+ kty: "RSA",
+ e: "AQAB",
+ n:
+ "vsC7BNafkRe8Uh1DUgCkv6RbPQMdJgAKKnWdSqQd7tQzU1mXfmo_k1Py_2MYMZXOWmqSZ9iwIYkykZYywJ2VyMGve4byj1sLn6YQoOkG8g5Z3V4y0S2RpEfmYumNjTzfq8nxtLnwjaYd4sCUd5wa0SzeLrpRQuXo2bF3QuUF2xcbLJloxX1MmlsMMCdBc-qGNonLJ7bpn_JuyXlDWy1Fkeyw1qgjiOdiRIbMC1x302zgzX6dSrBrNB8Cpsh-vCE0ZjUo8M9caEv06F6QbYmdGJHM0ZZY34OHMSNdf-_qUKIV_SuxuSuFE99tkAeWnbWpyI1V-xhVo1sc7NzChP8ci2TdPvI3_0JyAuCvL6zIFqJUJkZibEUghhg6F09-oNJKpy7rhUJq7zZyLXJsvuXnn0gnIxfjRvMcDfZAKUVMZKRdw7fwWzwQril4Ib0MQOVda9vb_4JMk7Gup-TUI4sfuS4NKwsnKoODIO-2U5QpJWdtp1F4AQ1pBv8ajFl1WTrVGvkRGK0woPWaO6pWyJ4kRnhnxrV2FyNNt3JSR-0JEjhFWws47kjBvpr0VRiVRFppKA-plKs4LPlaaCff39TleYmY3mETe3w1GIGc2Lliad32Jpbx496IgDe1K3FMBEoKFZfhmtlRSXft8NKgSzPt2zkatM9bFKfaCYRaSy7akbk",
+ },
+ update_path_prefix: "https://securedrop.org/https-everywhere-2021/",
+ 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 {
+ // Delete the previous channel signing key, and add the new one below.
+ await this._sendMessage(
+ "delete_update_channel",
+ SECUREDROP_TOR_ONION_CHANNEL_2020.name
+ );
+ } catch (e) {
+ if (retries <= 0) {
+ throw new Error("Could not uninstall SecureDropTorOnion update channel");
+ }
+ await this.installTorOnionUpdateChannel(retries - 1);
+ return;
+ }
+
+ 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_update_channel_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 7e103239c8d6..e4b6d73f8f40 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 db1c497bcace..13b1279105f2 100644
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -277,7 +277,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);
@@ -1541,7 +1544,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 8731e44dd4eb..bf9639c82612 100644
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5657,6 +5657,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.
@@ -8693,6 +8697,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, NS_LITERAL_CSTRING(".tor.onion")) &&
+ NS_SUCCEEDED(aNewURI->GetHost(newHost)) &&
+ StringEndsWith(newHost, NS_LITERAL_CSTRING(".onion")) &&
+ !StringEndsWith(newHost, NS_LITERAL_CSTRING(".tor.onion"))) {
+ return true;
+ }
+ return false;
+ }
+
nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
nsIDocShell** aDocShell,
nsIRequest** aRequest) {
@@ -8844,6 +8862,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
@@ -10996,6 +11038,7 @@ nsresult nsDocShell::AddToSessionHistory(
mDynamicallyCreated, originalURI, resultPrincipalURI,
loadReplace, referrerInfo, srcdoc, srcdocEntry, baseURI,
saveLayoutState, expired);
+ entry->SetOnionUrlbarRewritesAllowed(mOnionUrlbarRewritesAllowed);
if (root == static_cast<nsIDocShellTreeItem*>(this) && GetSessionHistory()) {
bool shouldPersist = ShouldAddToSessionHistory(aURI, aChannel);
@@ -12793,3 +12836,12 @@ bool nsDocShell::GetIsAttemptingToNavigate() {
return false;
}
+
+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 dcffed8e5537..d403a06f4c9f 100644
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -152,6 +152,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
@@ -582,6 +585,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,
@@ -1264,6 +1269,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 d91bb6034f9d..fe1216bd2509 100644
--- a/docshell/base/nsDocShellLoadState.cpp
+++ b/docshell/base/nsDocShellLoadState.cpp
@@ -730,6 +730,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 01dc4734eb19..305acfd3a98f 100644
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -1019,4 +1019,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 bbc3eb7583a2..a4cceb52b2a0 100644
--- a/docshell/base/nsIWebNavigation.idl
+++ b/docshell/base/nsIWebNavigation.idl
@@ -237,6 +237,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 07a28d1291c9..566b7d68fae5 100644
--- a/docshell/shistory/SessionHistoryEntry.cpp
+++ b/docshell/shistory/SessionHistoryEntry.cpp
@@ -490,6 +490,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 fa6108952688..c51682a65b0a 100644
--- a/docshell/shistory/SessionHistoryEntry.h
+++ b/docshell/shistory/SessionHistoryEntry.h
@@ -58,6 +58,7 @@ class SessionHistoryInfo {
bool mIsSrcdocEntry = false;
bool mScrollRestorationIsManual = false;
bool mPersist = false;
+ bool mOnionUrlbarRewritesAllowed = false;
};
// XXX Not sure that the id shouldn't just live in SessionHistoryInfo.
diff --git a/docshell/shistory/nsISHEntry.idl b/docshell/shistory/nsISHEntry.idl
index 8bbf2a68687c..bf410bfcc05a 100644
--- a/docshell/shistory/nsISHEntry.idl
+++ b/docshell/shistory/nsISHEntry.idl
@@ -242,6 +242,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 229b15eff25d..12d38f4c55fe 100644
--- a/docshell/shistory/nsSHEntry.cpp
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -44,7 +44,8 @@ nsSHEntry::nsSHEntry(nsISHistory* aSHistory)
mIsSrcdocEntry(false),
mScrollRestorationIsManual(false),
mLoadedInThisProcess(false),
- mPersist(true) {}
+ mPersist(true),
+ mOnionUrlbarRewritesAllowed(false) {}
nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
: mShared(aOther.mShared),
@@ -70,7 +71,8 @@ nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
mIsSrcdocEntry(aOther.mIsSrcdocEntry),
mScrollRestorationIsManual(false),
mLoadedInThisProcess(aOther.mLoadedInThisProcess),
- mPersist(aOther.mPersist) {}
+ mPersist(aOther.mPersist),
+ mOnionUrlbarRewritesAllowed(aOther.mOnionUrlbarRewritesAllowed) {}
nsSHEntry::~nsSHEntry() {
// Null out the mParent pointers on all our kids.
@@ -824,6 +826,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();
@@ -873,6 +887,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 351f6f200a33..6bc9de521437 100644
--- a/docshell/shistory/nsSHEntry.h
+++ b/docshell/shistory/nsSHEntry.h
@@ -63,6 +63,7 @@ class nsSHEntry : public nsISHEntry {
bool mScrollRestorationIsManual;
bool mLoadedInThisProcess;
bool mPersist;
+ bool mOnionUrlbarRewritesAllowed;
};
#endif /* nsSHEntry_h */
diff --git a/dom/interfaces/base/nsIBrowser.idl b/dom/interfaces/base/nsIBrowser.idl
index 300b09e13824..a9ff00e964b7 100644
--- a/dom/interfaces/base/nsIBrowser.idl
+++ b/dom/interfaces/base/nsIBrowser.idl
@@ -158,7 +158,8 @@ interface nsIBrowser : nsISupports
in uint64_t aInnerWindowID,
in boolean aHasRequestContextID,
in uint64_t aRequestContextID,
- in AString aContentType);
+ in AString aContentType,
+ in boolean aOnionUrlbarRewritesAllowed);
/**
* Called by Gecko when it wants to change the process which is currently
diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp
index 2b17323d8a93..f72aa8faa11d 100644
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -3645,6 +3645,8 @@ NS_IMETHODIMP BrowserChild::OnLocationChange(nsIWebProgress* aWebProgress,
docShell->GetMayEnableCharacterEncodingMenu();
locationChangeData->charsetAutodetected() =
docShell->GetCharsetAutodetected();
+ locationChangeData->onionUrlbarRewritesAllowed() =
+ docShell->GetOnionUrlbarRewritesAllowed();
locationChangeData->contentPrincipal() = document->NodePrincipal();
locationChangeData->contentStoragePrincipal() =
diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp
index 7749792cafb4..086ecd95e9e7 100644
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -2698,7 +2698,8 @@ mozilla::ipc::IPCResult BrowserParent::RecvOnLocationChange(
aWebProgressData->innerDOMWindowID(),
aLocationChangeData->requestContextID().isSome(),
aLocationChangeData->requestContextID().valueOr(0),
- aLocationChangeData->contentType());
+ aLocationChangeData->contentType(),
+ aLocationChangeData->onionUrlbarRewritesAllowed());
}
Unused << managerAsListener->OnLocationChange(webProgress, request, aLocation,
diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl
index f36e2c6db353..15756e7fef4f 100644
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -142,6 +142,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 fa5628a01c94..1e8dc23fdfd7 100644
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -1077,6 +1077,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 0bb881cfa639..fe680911ee0b 100644
--- a/netwerk/dns/effective_tld_names.dat
+++ b/netwerk/dns/effective_tld_names.dat
@@ -5518,6 +5518,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 901ce11b57ca..988ec94a82d4 100644
--- a/netwerk/ipc/DocumentLoadListener.cpp
+++ b/netwerk/ipc/DocumentLoadListener.cpp
@@ -1856,6 +1856,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 62a1ab1d6796..67fa1e5645c0 100644
--- a/toolkit/content/widgets/browser-custom-element.js
+++ b/toolkit/content/widgets/browser-custom-element.js
@@ -261,6 +261,8 @@
this._mayEnableCharacterEncodingMenu = null;
+ this._onionUrlbarRewritesAllowed = false;
+
this._charsetAutodetected = false;
this._contentPrincipal = null;
@@ -686,6 +688,12 @@
}
}
+ get onionUrlbarRewritesAllowed() {
+ return this.isRemoteBrowser
+ ? this._onionUrlbarRewritesAllowed
+ : this.docShell.onionUrlbarRewritesAllowed;
+ }
+
get charsetAutodetected() {
return this.isRemoteBrowser
? this._charsetAutodetected
@@ -1292,7 +1300,8 @@
aInnerWindowID,
aHaveRequestContextID,
aRequestContextID,
- aContentType
+ aContentType,
+ aOnionUrlbarRewritesAllowed
) {
if (this.isRemoteBrowser && this.messageManager) {
if (aCharset != null) {
@@ -1316,6 +1325,7 @@
this._contentRequestContextID = aHaveRequestContextID
? aRequestContextID
: null;
+ this._onionUrlbarRewritesAllowed = aOnionUrlbarRewritesAllowed;
}
}
@@ -1708,6 +1718,7 @@
"_contentStoragePrincipal",
"_isSyntheticDocument",
"_innerWindowID",
+ "_onionUrlbarRewritesAllowed",
]
);
}
diff --git a/toolkit/modules/sessionstore/SessionHistory.jsm b/toolkit/modules/sessionstore/SessionHistory.jsm
index 6c16ac331659..cc47f437824f 100644
--- a/toolkit/modules/sessionstore/SessionHistory.jsm
+++ b/toolkit/modules/sessionstore/SessionHistory.jsm
@@ -318,6 +318,7 @@ var SessionHistoryInternal = {
}
entry.persist = shEntry.persist;
+ entry.onionUrlbarRewritesAllowed = shEntry.onionUrlbarRewritesAllowed;
return entry;
},
@@ -601,6 +602,10 @@ var SessionHistoryInternal = {
}
}
+ if (entry.onionUrlbarRewritesAllowed) {
+ shEntry.onionUrlbarRewritesAllowed = entry.onionUrlbarRewritesAllowed;
+ }
+
return shEntry;
},
1
0

[tor-browser/tor-browser-78.14.0esr-10.5-1] 40209: Implement Basic Crypto Safety
by sysrqb@torproject.org 04 Sep '21
by sysrqb@torproject.org 04 Sep '21
04 Sep '21
commit 2bffa45450af4da3c126da12e919542aef295a9b
Author: sanketh <me(a)snkth.com>
Date: Mon Feb 8 20:12:44 2021 -0500
40209: Implement Basic Crypto Safety
Adds a CryptoSafety actor which detects when you've copied a crypto
address from a HTTP webpage and shows a warning.
Closes #40209.
Bug 40428: Fix string attribute names
---
browser/actors/CryptoSafetyChild.jsm | 87 ++++++++++++++++
browser/actors/CryptoSafetyParent.jsm | 142 +++++++++++++++++++++++++++
browser/actors/moz.build | 2 +
browser/base/content/popup-notifications.inc | 14 +++
browser/components/BrowserGlue.jsm | 17 ++++
browser/modules/TorStrings.jsm | 48 +++++++++
browser/themes/shared/browser.inc.css | 5 +
toolkit/content/license.html | 32 ++++++
toolkit/modules/Bech32Decode.jsm | 103 +++++++++++++++++++
toolkit/modules/moz.build | 1 +
10 files changed, 451 insertions(+)
diff --git a/browser/actors/CryptoSafetyChild.jsm b/browser/actors/CryptoSafetyChild.jsm
new file mode 100644
index 000000000000..87ff261d4915
--- /dev/null
+++ b/browser/actors/CryptoSafetyChild.jsm
@@ -0,0 +1,87 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Copyright (c) 2020, The Tor Project, Inc.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var EXPORTED_SYMBOLS = ["CryptoSafetyChild"];
+
+const { Bech32Decode } = ChromeUtils.import(
+ "resource://gre/modules/Bech32Decode.jsm"
+);
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+const kPrefCryptoSafety = "security.cryptoSafety";
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "isCryptoSafetyEnabled",
+ kPrefCryptoSafety,
+ true /* defaults to true */
+);
+
+function looksLikeCryptoAddress(s) {
+ // P2PKH and P2SH addresses
+ // https://stackoverflow.com/a/24205650
+ const bitcoinAddr = /^[13][a-km-zA-HJ-NP-Z1-9]{25,39}$/;
+ if (bitcoinAddr.test(s)) {
+ return true;
+ }
+
+ // Bech32 addresses
+ if (Bech32Decode(s) !== null) {
+ return true;
+ }
+
+ // regular addresses
+ const etherAddr = /^0x[a-fA-F0-9]{40}$/;
+ if (etherAddr.test(s)) {
+ return true;
+ }
+
+ // t-addresses
+ // https://www.reddit.com/r/zec/comments/8mxj6x/simple_regex_to_validate_a_zca…
+ const zcashAddr = /^t1[a-zA-Z0-9]{33}$/;
+ if (zcashAddr.test(s)) {
+ return true;
+ }
+
+ // Standard, Integrated, and 256-bit Integrated addresses
+ // https://monero.stackexchange.com/a/10627
+ const moneroAddr = /^4(?:[0-9AB]|[1-9A-HJ-NP-Za-km-z]{12}(?:[1-9A-HJ-NP-Za-km-z]{30})?)[1-9A-HJ-NP-Za-km-z]{93}$/;
+ if (moneroAddr.test(s)) {
+ return true;
+ }
+
+ return false;
+}
+
+class CryptoSafetyChild extends JSWindowActorChild {
+ handleEvent(event) {
+ if (isCryptoSafetyEnabled) {
+ // Ignore non-HTTP addresses
+ if (!this.document.documentURIObject.schemeIs("http")) {
+ return;
+ }
+ // Ignore onion addresses
+ if (this.document.documentURIObject.host.endsWith(".onion")) {
+ return;
+ }
+
+ if (event.type == "copy" || event.type == "cut") {
+ this.contentWindow.navigator.clipboard.readText().then(clipText => {
+ const selection = clipText.trim();
+ if (looksLikeCryptoAddress(selection)) {
+ this.sendAsyncMessage("CryptoSafety:CopiedText", {
+ selection,
+ });
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/browser/actors/CryptoSafetyParent.jsm b/browser/actors/CryptoSafetyParent.jsm
new file mode 100644
index 000000000000..bac151df5511
--- /dev/null
+++ b/browser/actors/CryptoSafetyParent.jsm
@@ -0,0 +1,142 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Copyright (c) 2020, The Tor Project, Inc.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var EXPORTED_SYMBOLS = ["CryptoSafetyParent"];
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ TorStrings: "resource:///modules/TorStrings.jsm",
+});
+
+const kPrefCryptoSafety = "security.cryptoSafety";
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "isCryptoSafetyEnabled",
+ kPrefCryptoSafety,
+ true /* defaults to true */
+);
+
+class CryptoSafetyParent extends JSWindowActorParent {
+ getBrowser() {
+ return this.browsingContext.top.embedderElement;
+ }
+
+ receiveMessage(aMessage) {
+ if (isCryptoSafetyEnabled) {
+ if (aMessage.name == "CryptoSafety:CopiedText") {
+ showPopup(this.getBrowser(), aMessage.data.selection);
+ }
+ }
+ }
+}
+
+function trimAddress(cryptoAddr) {
+ if (cryptoAddr.length <= 32) {
+ return cryptoAddr;
+ }
+ return cryptoAddr.substring(0, 32) + "...";
+}
+
+function showPopup(aBrowser, cryptoAddr) {
+ const chromeDoc = aBrowser.ownerDocument;
+ if (chromeDoc) {
+ const win = chromeDoc.defaultView;
+ const cryptoSafetyPrompt = new CryptoSafetyPrompt(
+ aBrowser,
+ win,
+ cryptoAddr
+ );
+ cryptoSafetyPrompt.show();
+ }
+}
+
+class CryptoSafetyPrompt {
+ constructor(aBrowser, aWin, cryptoAddr) {
+ this._browser = aBrowser;
+ this._win = aWin;
+ this._cryptoAddr = cryptoAddr;
+ }
+
+ show() {
+ const primaryAction = {
+ label: TorStrings.cryptoSafetyPrompt.primaryAction,
+ accessKey: TorStrings.cryptoSafetyPrompt.primaryActionAccessKey,
+ callback: () => {
+ this._win.torbutton_new_circuit();
+ },
+ };
+
+ const secondaryAction = {
+ label: TorStrings.cryptoSafetyPrompt.secondaryAction,
+ accessKey: TorStrings.cryptoSafetyPrompt.secondaryActionAccessKey,
+ callback: () => {},
+ };
+
+ let _this = this;
+ const options = {
+ popupIconURL: "chrome://browser/skin/cert-error.svg",
+ eventCallback(aTopic) {
+ if (aTopic === "showing") {
+ _this._onPromptShowing();
+ }
+ },
+ };
+
+ const cryptoWarningText = TorStrings.cryptoSafetyPrompt.cryptoWarning.replace(
+ "%S",
+ trimAddress(this._cryptoAddr)
+ );
+
+ if (this._win.PopupNotifications) {
+ this._prompt = this._win.PopupNotifications.show(
+ this._browser,
+ "crypto-safety-warning",
+ cryptoWarningText,
+ null /* anchor ID */,
+ primaryAction,
+ [secondaryAction],
+ options
+ );
+ }
+ }
+
+ _onPromptShowing() {
+ let xulDoc = this._browser.ownerDocument;
+
+ let whatCanHeading = xulDoc.getElementById(
+ "crypto-safety-warning-notification-what-can-heading"
+ );
+ if (whatCanHeading) {
+ whatCanHeading.textContent = TorStrings.cryptoSafetyPrompt.whatCanHeading;
+ }
+
+ let whatCanBody = xulDoc.getElementById(
+ "crypto-safety-warning-notification-what-can-body"
+ );
+ if (whatCanBody) {
+ whatCanBody.textContent = TorStrings.cryptoSafetyPrompt.whatCanBody;
+ }
+
+ let learnMoreElem = xulDoc.getElementById(
+ "crypto-safety-warning-notification-learnmore"
+ );
+ if (learnMoreElem) {
+ learnMoreElem.setAttribute(
+ "value",
+ TorStrings.cryptoSafetyPrompt.learnMore
+ );
+ learnMoreElem.setAttribute(
+ "href",
+ TorStrings.cryptoSafetyPrompt.learnMoreURL
+ );
+ }
+ }
+}
diff --git a/browser/actors/moz.build b/browser/actors/moz.build
index e70f0f09fe3a..9eb5ca397060 100644
--- a/browser/actors/moz.build
+++ b/browser/actors/moz.build
@@ -50,6 +50,8 @@ FINAL_TARGET_FILES.actors += [
'ContentSearchParent.jsm',
'ContextMenuChild.jsm',
'ContextMenuParent.jsm',
+ 'CryptoSafetyChild.jsm',
+ 'CryptoSafetyParent.jsm',
'DOMFullscreenChild.jsm',
'DOMFullscreenParent.jsm',
'FormValidationChild.jsm',
diff --git a/browser/base/content/popup-notifications.inc b/browser/base/content/popup-notifications.inc
index 42e17e90c648..ff6f8cdeca80 100644
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -114,3 +114,17 @@
</vbox>
</popupnotificationfooter>
</popupnotification>
+
+ <popupnotification id="crypto-safety-warning-notification" hidden="true">
+ <popupnotificationcontent orient="vertical">
+ <description id="crypto-safety-warning-notification-desc"/>
+ <html:div id="crypto-safety-warning-notification-what-can">
+ <html:strong id="crypto-safety-warning-notification-what-can-heading" />
+ <html:br/>
+ <html:span id="crypto-safety-warning-notification-what-can-body" />
+ </html:div>
+ <label id="crypto-safety-warning-notification-learnmore"
+ class="popup-notification-learnmore-link"
+ is="text-link"/>
+ </popupnotificationcontent>
+ </popupnotification>
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 3750230a250b..5f708fca3d5c 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -297,6 +297,23 @@ let JSWINDOWACTORS = {
allFrames: true,
},
+ CryptoSafety: {
+ parent: {
+ moduleURI: "resource:///actors/CryptoSafetyParent.jsm",
+ },
+
+ child: {
+ moduleURI: "resource:///actors/CryptoSafetyChild.jsm",
+ group: "browsers",
+ events: {
+ copy: { mozSystemGroup: true },
+ cut: { mozSystemGroup: true },
+ },
+ },
+
+ allFrames: true,
+ },
+
DOMFullscreen: {
parent: {
moduleURI: "resource:///actors/DOMFullscreenParent.jsm",
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm
index e8a8d37ae373..1e08b168e4af 100644
--- a/browser/modules/TorStrings.jsm
+++ b/browser/modules/TorStrings.jsm
@@ -101,6 +101,54 @@ class TorPropertyStringBundle {
Security Level Strings
*/
var TorStrings = {
+ /*
+ CryptoSafetyPrompt Strings
+ */
+ cryptoSafetyPrompt: (function() {
+ let tsb = new TorPropertyStringBundle(
+ "chrome://torbutton/locale/torbutton.properties",
+ "cryptoSafetyPrompt."
+ );
+ let getString = function(key, fallback) {
+ return tsb.getString(key, fallback);
+ };
+
+ let retval = {
+ cryptoWarning: getString(
+ "cryptoWarning",
+ "A cryptocurrency address (%S) has been copied from an insecure website. It could have been modified."
+ ),
+ whatCanHeading: getString(
+ "whatCanHeading",
+ "What can you do about it?"
+ ),
+ whatCanBody: getString(
+ "whatCanBody",
+ "You can try reconnecting with a new circuit to establish a secure connection, or accept the risk and dismiss this warning."
+ ),
+ learnMore: getString("learnMore", "Learn more"),
+ learnMoreURL: `https://support.torproject.org/${getLocale()}/`,
+ primaryAction: getString(
+ "primaryAction",
+ "Reload Tab with a New Circuit"
+ ),
+ primaryActionAccessKey: getString(
+ "primaryActionAccessKey",
+ "R"
+ ),
+ secondaryAction: getString(
+ "secondaryAction",
+ "Dismiss Warning"
+ ),
+ secondaryActionAccessKey: getString(
+ "secondaryActionAccessKey",
+ "D"
+ ),
+ };
+
+ return retval;
+ })() /* CryptoSafetyPrompt Strings */,
+
/*
Tor Browser Security Level Strings
*/
diff --git a/browser/themes/shared/browser.inc.css b/browser/themes/shared/browser.inc.css
index 0113466e8e56..4ef27d880754 100644
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -620,3 +620,8 @@ menupopup::part(drop-indicator) {
#sharing-warning-proceed-to-tab:hover {
background-color: rgb(0,62,170);
}
+
+#crypto-safety-warning-notification-what-can {
+ display: block;
+ margin: 5px;
+}
diff --git a/toolkit/content/license.html b/toolkit/content/license.html
index e44c31ec6d4e..90995236b41b 100644
--- a/toolkit/content/license.html
+++ b/toolkit/content/license.html
@@ -72,6 +72,7 @@
<li><a href="about:license#arm">ARM License</a></li>
<li><a href="about:license#babel">Babel License</a></li>
<li><a href="about:license#babylon">Babylon License</a></li>
+ <li><a href="about:license#bech32">Bech32 License</a></li>
<li><a href="about:license#bincode">bincode License</a></li>
<li><a href="about:license#bsd2clause">BSD 2-Clause License</a></li>
<li><a href="about:license#bsd3clause">BSD 3-Clause License</a></li>
@@ -2795,6 +2796,37 @@ furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="bech32"></a>Bech32 License</h1>
+
+ <p>This license applies to the file
+ <code>toolkit/modules/Bech32Decode.jsm</code>.
+ </p>
+
+<pre>
+Copyright (c) 2017 Pieter Wuille
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
diff --git a/toolkit/modules/Bech32Decode.jsm b/toolkit/modules/Bech32Decode.jsm
new file mode 100644
index 000000000000..3a2bc7ae0a10
--- /dev/null
+++ b/toolkit/modules/Bech32Decode.jsm
@@ -0,0 +1,103 @@
+// Adapted from the reference implementation of Bech32
+// https://github.com/sipa/bech32
+
+// Copyright (c) 2017 Pieter Wuille
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+"use strict";
+
+/**
+ * JS module implementation of Bech32 decoding adapted from the reference
+ * implementation https://github.com/sipa/bech32.
+ */
+
+var EXPORTED_SYMBOLS = ["Bech32Decode"];
+
+var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
+var GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
+
+function polymod(values) {
+ var chk = 1;
+ for (var p = 0; p < values.length; ++p) {
+ var top = chk >> 25;
+ chk = ((chk & 0x1ffffff) << 5) ^ values[p];
+ for (var i = 0; i < 5; ++i) {
+ if ((top >> i) & 1) {
+ chk ^= GENERATOR[i];
+ }
+ }
+ }
+ return chk;
+}
+
+function hrpExpand(hrp) {
+ var ret = [];
+ var p;
+ for (p = 0; p < hrp.length; ++p) {
+ ret.push(hrp.charCodeAt(p) >> 5);
+ }
+ ret.push(0);
+ for (p = 0; p < hrp.length; ++p) {
+ ret.push(hrp.charCodeAt(p) & 31);
+ }
+ return ret;
+}
+
+function verifyChecksum(hrp, data) {
+ return polymod(hrpExpand(hrp).concat(data)) === 1;
+}
+
+function Bech32Decode(bechString) {
+ var p;
+ var has_lower = false;
+ var has_upper = false;
+ for (p = 0; p < bechString.length; ++p) {
+ if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) {
+ return null;
+ }
+ if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) {
+ has_lower = true;
+ }
+ if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) {
+ has_upper = true;
+ }
+ }
+ if (has_lower && has_upper) {
+ return null;
+ }
+ bechString = bechString.toLowerCase();
+ var pos = bechString.lastIndexOf("1");
+ if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) {
+ return null;
+ }
+ var hrp = bechString.substring(0, pos);
+ var data = [];
+ for (p = pos + 1; p < bechString.length; ++p) {
+ var d = CHARSET.indexOf(bechString.charAt(p));
+ if (d === -1) {
+ return null;
+ }
+ data.push(d);
+ }
+ if (!verifyChecksum(hrp, data)) {
+ return null;
+ }
+ return { hrp: hrp, data: data.slice(0, data.length - 6) };
+}
diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build
index e1f1eb5759c5..698d2773a7ed 100644
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -160,6 +160,7 @@ EXTRA_JS_MODULES += [
'ActorManagerParent.jsm',
'AppMenuNotifications.jsm',
'AsyncPrefs.jsm',
+ 'Bech32Decode.jsm',
'BinarySearch.jsm',
'BrowserUtils.jsm',
'CanonicalJSON.jsm',
1
0

[tor-browser/tor-browser-78.14.0esr-10.5-1] Bug 40475: Include clearing CORS preflight cache
by sysrqb@torproject.org 04 Sep '21
by sysrqb@torproject.org 04 Sep '21
04 Sep '21
commit 85898f8cdd7462c0a619d8b91ba293dc3afe399a
Author: Matthew Finkel <sysrqb(a)torproject.org>
Date: Sun Jun 6 20:32:23 2021 +0000
Bug 40475: Include clearing CORS preflight cache
---
netwerk/protocol/http/nsCORSListenerProxy.cpp | 7 +++++++
netwerk/protocol/http/nsCORSListenerProxy.h | 1 +
netwerk/protocol/http/nsHttpHandler.cpp | 2 ++
3 files changed, 10 insertions(+)
diff --git a/netwerk/protocol/http/nsCORSListenerProxy.cpp b/netwerk/protocol/http/nsCORSListenerProxy.cpp
index 76870e6cea3f..6d2e160c2a9b 100644
--- a/netwerk/protocol/http/nsCORSListenerProxy.cpp
+++ b/netwerk/protocol/http/nsCORSListenerProxy.cpp
@@ -346,6 +346,13 @@ void nsCORSListenerProxy::Shutdown() {
sPreflightCache = nullptr;
}
+/* static */
+void nsCORSListenerProxy::Clear() {
+ if (sPreflightCache) {
+ sPreflightCache->Clear();
+ }
+}
+
nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter,
nsIPrincipal* aRequestingPrincipal,
bool aWithCredentials)
diff --git a/netwerk/protocol/http/nsCORSListenerProxy.h b/netwerk/protocol/http/nsCORSListenerProxy.h
index 8c0df2e0ff28..3f76be33f209 100644
--- a/netwerk/protocol/http/nsCORSListenerProxy.h
+++ b/netwerk/protocol/http/nsCORSListenerProxy.h
@@ -54,6 +54,7 @@ class nsCORSListenerProxy final : public nsIStreamListener,
NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
static void Shutdown();
+ static void Clear();
[[nodiscard]] nsresult Init(nsIChannel* aChannel,
DataURIHandling aAllowDataURI);
diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp
index d5e2c61dbec9..c6cb95ca7fcc 100644
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -10,6 +10,7 @@
#include "prsystem.h"
#include "AltServiceChild.h"
+#include "nsCORSListenerProxy.h"
#include "nsError.h"
#include "nsHttp.h"
#include "nsHttpHandler.h"
@@ -2290,6 +2291,7 @@ nsHttpHandler::Observe(nsISupports* subject, const char* topic,
mAltSvcCache->ClearAltServiceMappings();
}
}
+ nsCORSListenerProxy::Clear();
} else if (!strcmp(topic, NS_NETWORK_LINK_TOPIC)) {
nsAutoCString converted = NS_ConvertUTF16toUTF8(data);
if (!strcmp(converted.get(), NS_NETWORK_LINK_DATA_CHANGED)) {
1
0

[tor-browser/tor-browser-78.14.0esr-10.5-1] Bug 1650281 - P2: Make sure `gCombinedSizes` won't be underflowed r=gerald
by sysrqb@torproject.org 04 Sep '21
by sysrqb@torproject.org 04 Sep '21
04 Sep '21
commit 63be947e830fb4054cf43fd233585bbd94721775
Author: Chun-Min Chang <chun.m.chang(a)gmail.com>
Date: Tue Jul 21 23:39:14 2020 +0000
Bug 1650281 - P2: Make sure `gCombinedSizes` won't be underflowed r=gerald
In any case, `gCombinedSizes` should be larger than or equal to the
buffer within `MemoryClockCache`.
Differential Revision: https://phabricator.services.mozilla.com/D84274
---
dom/media/MemoryBlockCache.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/dom/media/MemoryBlockCache.cpp b/dom/media/MemoryBlockCache.cpp
index bf073e6769d0..2848a3f3812c 100644
--- a/dom/media/MemoryBlockCache.cpp
+++ b/dom/media/MemoryBlockCache.cpp
@@ -52,6 +52,7 @@ MemoryBlockCache::MemoryBlockCache(int64_t aContentLength)
}
MemoryBlockCache::~MemoryBlockCache() {
+ MOZ_ASSERT(gCombinedSizes >= mBuffer.Length());
size_t sizes = static_cast<size_t>(gCombinedSizes -= mBuffer.Length());
LOG("~MemoryBlockCache() - destroying buffer of size %zu; combined sizes now "
"%zu",
1
0

[tor-browser/tor-browser-78.14.0esr-10.5-1] Bug 27476: Implement about:torconnect captive portal within Tor Browser
by sysrqb@torproject.org 04 Sep '21
by sysrqb@torproject.org 04 Sep '21
04 Sep '21
commit 89eda798a60a55bb2d64a9011f0b03415470c5bb
Author: Richard Pospesel <richard(a)torproject.org>
Date: Wed Apr 28 23:09:34 2021 -0500
Bug 27476: Implement about:torconnect captive portal within Tor Browser
- implements new about:torconnect page as tor-launcher replacement
- adds tor connection status to url bar and tweaks UX when not online
- adds new torconnect component to browser
- tor process management functionality remains implemented in tor-launcher through the TorProtocolService module
- the onion pattern from about:tor migrated to an .inc.xhtml file now used by both about:tor and about:torconnect
- various design tweaks and resusability fixes to onion pattern
- adds warning/error box to about:preferences#tor when not connected to tor
- explicitly allows about:torconnect URIs to ignore Resist Fingerprinting (RFP)
- various tweaks to info-pages.inc.css for about:torconnect (also affects other firefox info pages)
---
browser/actors/NetErrorParent.jsm | 8 +
browser/base/content/aboutNetError.js | 10 +-
browser/base/content/browser-siteIdentity.js | 2 +-
browser/base/content/browser.js | 10 +
browser/base/content/browser.xhtml | 2 +
browser/components/BrowserGlue.jsm | 59 +++-
browser/components/about/AboutRedirector.cpp | 4 +
browser/components/about/components.conf | 1 +
browser/components/moz.build | 1 +
.../onionservices/HttpsEverywhereControl.jsm | 17 +-
browser/components/sessionstore/SessionStore.jsm | 16 +
browser/components/torconnect/TorConnectChild.jsm | 9 +
browser/components/torconnect/TorConnectParent.jsm | 126 ++++++++
.../torconnect/content/aboutTorConnect.css | 151 +++++++++
.../torconnect/content/aboutTorConnect.js | 339 +++++++++++++++++++++
.../torconnect/content/aboutTorConnect.xhtml | 54 ++++
.../components/torconnect/content/onion-slash.svg | 7 +
browser/components/torconnect/content/onion.svg | 3 +
.../torconnect/content/torBootstrapUrlbar.js | 136 +++++++++
.../torconnect/content/torconnect-urlbar.css | 65 ++++
.../torconnect/content/torconnect-urlbar.inc.xhtml | 11 +
browser/components/torconnect/jar.mn | 7 +
browser/components/torconnect/moz.build | 6 +
.../components/torpreferences/content/torPane.js | 86 ++++++
.../torpreferences/content/torPane.xhtml | 34 +++
.../torpreferences/content/torPreferences.css | 121 ++++++++
browser/components/urlbar/UrlbarInput.jsm | 31 ++
browser/modules/TorConnect.jsm | 62 ++++
browser/modules/TorProcessService.jsm | 12 +
browser/modules/TorProtocolService.jsm | 124 +++++++-
browser/modules/TorStrings.jsm | 75 +++++
browser/modules/moz.build | 2 +
browser/themes/shared/jar.inc.mn | 1 +
browser/themes/shared/onionPattern.css | 124 ++++++++
browser/themes/shared/onionPattern.inc.xhtml | 210 +++++++++++++
browser/themes/shared/urlbar-searchbar.inc.css | 2 +
dom/base/Document.cpp | 51 +++-
dom/base/nsGlobalWindowOuter.cpp | 2 +
toolkit/modules/AsyncPrefs.jsm | 2 +
toolkit/modules/RemotePageAccessManager.jsm | 26 ++
toolkit/mozapps/update/UpdateService.jsm | 68 ++++-
.../themes/shared/in-content/info-pages.inc.css | 15 +-
.../lib/environments/browser-window.js | 4 +
43 files changed, 2080 insertions(+), 16 deletions(-)
diff --git a/browser/actors/NetErrorParent.jsm b/browser/actors/NetErrorParent.jsm
index 035195391554..fa3cbf23fcb7 100644
--- a/browser/actors/NetErrorParent.jsm
+++ b/browser/actors/NetErrorParent.jsm
@@ -17,6 +17,10 @@ const { SessionStore } = ChromeUtils.import(
);
const { HomePage } = ChromeUtils.import("resource:///modules/HomePage.jsm");
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+
const PREF_SSL_IMPACT_ROOTS = [
"security.tls.version.",
"security.ssl3.",
@@ -318,6 +322,10 @@ class NetErrorParent extends JSWindowActorParent {
break;
}
}
+ break;
+ case "ShouldShowTorConnect":
+ return TorProtocolService.shouldShowTorConnect();
}
+ return undefined;
}
}
diff --git a/browser/base/content/aboutNetError.js b/browser/base/content/aboutNetError.js
index 60db17f46eb9..6844154e16e3 100644
--- a/browser/base/content/aboutNetError.js
+++ b/browser/base/content/aboutNetError.js
@@ -194,8 +194,16 @@ async function setErrorPageStrings(err) {
document.l10n.setAttributes(titleElement, title);
}
-function initPage() {
+async function initPage() {
var err = getErrorCode();
+
+ // proxyConnectFailure because no-tor running daemon would return this error
+ if (
+ (err === "proxyConnectFailure") &&
+ (await RPMSendQuery("ShouldShowTorConnect"))
+ ) {
+ document.location.replace("about:torconnect");
+ }
// List of error pages with an illustration.
let illustratedErrors = [
"malformedURI",
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js
index 539d6d4056a3..2a3431172886 100644
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -57,7 +57,7 @@ var gIdentityHandler = {
* RegExp used to decide if an about url should be shown as being part of
* the browser UI.
*/
- _secureInternalUIWhitelist: (AppConstants.TOR_BROWSER_UPDATE ? /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|tor|tbupdate)(?:[?#]|$)/i : /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|tor)(?:[?#]|$)/i),
+ _secureInternalUIWhitelist: (AppConstants.TOR_BROWSER_UPDATE ? /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|tor|torconnect|tbupdate)(?:[?#]|$)/i : /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|tor|torconnect)(?:[?#]|$)/i),
/**
* Whether the established HTTPS connection is considered "broken".
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 04f8752b93f4..916cd69320cb 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -77,6 +77,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
TabModalPrompt: "chrome://global/content/tabprompts.jsm",
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
+ TorProtocolService: "resource:///modules/TorProtocolService.jsm",
Translation: "resource:///modules/translation/TranslationParent.jsm",
OnionAliasStore: "resource:///modules/OnionAliasStore.jsm",
UITour: "resource:///modules/UITour.jsm",
@@ -633,6 +634,7 @@ var gPageIcons = {
var gInitialPages = [
"about:tor",
+ "about:torconnect",
"about:blank",
"about:newtab",
"about:home",
@@ -1959,6 +1961,8 @@ var gBrowserInit = {
}
this._loadHandled = true;
+
+ TorBootstrapUrlbar.init();
},
_cancelDelayedStartup() {
@@ -2490,6 +2494,10 @@ var gBrowserInit = {
let uri = window.arguments[0];
let defaultArgs = BrowserHandler.defaultArgs;
+ if (TorProtocolService.shouldShowTorConnect()) {
+ return "about:torconnect";
+ }
+
// If the given URI is different from the homepage, we want to load it.
if (uri != defaultArgs) {
AboutNewTab.noteNonDefaultStartup();
@@ -2582,6 +2590,8 @@ var gBrowserInit = {
OnionAuthPrompt.uninit();
+ TorBootstrapUrlbar.uninit();
+
gAccessibilityServiceIndicator.uninit();
AccessibilityRefreshBlocker.uninit();
diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml
index c2caecc1a416..032db1967c69 100644
--- a/browser/base/content/browser.xhtml
+++ b/browser/base/content/browser.xhtml
@@ -112,6 +112,7 @@
Services.scriptloader.loadSubScript("chrome://browser/content/search/searchbar.js", this);
Services.scriptloader.loadSubScript("chrome://torbutton/content/tor-circuit-display.js", this);
Services.scriptloader.loadSubScript("chrome://torbutton/content/torbutton.js", this);
+ Services.scriptloader.loadSubScript("chrome://browser/content/torconnect/torBootstrapUrlbar.js", this);
window.onload = gBrowserInit.onLoad.bind(gBrowserInit);
window.onunload = gBrowserInit.onUnload.bind(gBrowserInit);
@@ -1055,6 +1056,7 @@
data-l10n-id="urlbar-go-button"/>
<hbox id="page-action-buttons" context="pageActionContextMenu">
<toolbartabstop/>
+#include ../../components/torconnect/content/torconnect-urlbar.inc.xhtml
<hbox id="contextual-feature-recommendation" role="button" hidden="true">
<hbox id="cfr-label-container">
<label id="cfr-label"/>
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 5f708fca3d5c..8735783cee2b 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -17,6 +17,31 @@ const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
+// TorProtocolService and TorConnect modules need to be lazily-loaded
+// here because they will trigger generation of the random password used
+// to talk to the tor daemon in tor-launcher. Generating the random
+// password will initialize the cryptographic service ( nsNSSComponent )
+//
+// If this service is init'd before the profile has been setup, it will
+// use the fallback init path which behaves as if security.nocertdb=true
+//
+// We make these module getters so init happens when they are needed
+// (when init'ing the OnionAliasStore). With theze getters, the password
+// generation is triggered in torbutton after the 'profile-after-change'
+// topic (so after the profile is initialized)
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "TorProtocolService",
+ "resource:///modules/TorProtocolService.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "TorConnect",
+ "resource:///modules/TorConnect.jsm"
+);
+
ChromeUtils.defineModuleGetter(
this,
"ActorManagerParent",
@@ -503,6 +528,20 @@ let JSWINDOWACTORS = {
allFrames: true,
},
+ TorConnect: {
+ parent: {
+ moduleURI: "resource:///modules/TorConnectParent.jsm",
+ },
+ child: {
+ moduleURI: "resource:///modules/TorConnectChild.jsm",
+ events: {
+ DOMWindowCreated: {},
+ },
+ },
+
+ matches: ["about:torconnect"],
+ },
+
Translation: {
parent: {
moduleURI: "resource:///modules/translation/TranslationParent.jsm",
@@ -2492,7 +2531,25 @@ BrowserGlue.prototype = {
{
task: () => {
- OnionAliasStore.init();
+ if (TorProtocolService.isBootstrapDone() || !TorProtocolService.ownsTorDaemon) {
+ // we will take this path when the user is using the legacy tor launcher or
+ // when Tor Browser didn't launch its own tor.
+ OnionAliasStore.init();
+ } else {
+ // this path is taken when using about:torconnect, we wait to init
+ // after we are bootstrapped and connected to tor
+ const topic = "torconnect:bootstrap-complete";
+ let bootstrapObserver = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic === topic) {
+ OnionAliasStore.init();
+ // we only need to init once, so remove ourselves as an obvserver
+ Services.obs.removeObserver(this, topic);
+ }
+ }
+ };
+ Services.obs.addObserver(bootstrapObserver, topic);
+ }
},
},
diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp
index e7c377d655e7..db5f3ead4bb8 100644
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -120,6 +120,10 @@ static const RedirEntry kRedirMap[] = {
nsIAboutModule::URI_MUST_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT},
#endif
+ {"torconnect", "chrome://browser/content/torconnect/aboutTorConnect.xhtml",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT},
};
static nsAutoCString GetAboutModuleName(nsIURI* aURI) {
diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf
index 8e04467c05da..01c99ad4ed0c 100644
--- a/browser/components/about/components.conf
+++ b/browser/components/about/components.conf
@@ -26,6 +26,7 @@ pages = [
'robots',
'sessionrestore',
'tabcrashed',
+ 'torconnect',
'welcome',
'welcomeback',
]
diff --git a/browser/components/moz.build b/browser/components/moz.build
index b660be047b14..fb90c499c616 100644
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -59,6 +59,7 @@ DIRS += [
'syncedtabs',
'uitour',
'urlbar',
+ 'torconnect',
'torpreferences',
'translation',
]
diff --git a/browser/components/onionservices/HttpsEverywhereControl.jsm b/browser/components/onionservices/HttpsEverywhereControl.jsm
index 525ed5233be7..d673de4cd6e5 100644
--- a/browser/components/onionservices/HttpsEverywhereControl.jsm
+++ b/browser/components/onionservices/HttpsEverywhereControl.jsm
@@ -41,6 +41,7 @@ const SECUREDROP_TOR_ONION_CHANNEL = {
class HttpsEverywhereControl {
constructor() {
this._extensionMessaging = null;
+ this._init();
}
async _sendMessage(type, object) {
@@ -61,7 +62,6 @@ class HttpsEverywhereControl {
* 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.
@@ -143,5 +143,20 @@ class HttpsEverywhereControl {
if (!this._extensionMessaging) {
this._extensionMessaging = new ExtensionMessaging();
}
+
+ // update all of the existing https-everywhere channels
+ setTimeout(async () => {
+ let pinnedChannels = await this._sendMessage("get_pinned_update_channels");
+ for(let channel of pinnedChannels.update_channels) {
+ this._sendMessage("update_update_channel", channel);
+ }
+
+ let storedChannels = await this._sendMessage("get_stored_update_channels");
+ for(let channel of storedChannels.update_channels) {
+ this._sendMessage("update_update_channel", channel);
+ }
+ }, 0);
+
+
}
}
diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm
index 6ab86fd5913e..2d66422abf58 100644
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -213,6 +213,10 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/sessionstore/SessionHistory.jsm"
);
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+
XPCOMUtils.defineLazyServiceGetters(this, {
gScreenManager: ["@mozilla.org/gfx/screenmanager;1", "nsIScreenManager"],
Telemetry: ["@mozilla.org/base/telemetry;1", "nsITelemetry"],
@@ -1888,11 +1892,23 @@ var SessionStoreInternal = {
}, "browser-delayed-startup-finished");
});
+ let bootstrapPromise = new Promise(resolve => {
+ if (TorProtocolService.isBootstrapDone() || !TorProtocolService.ownsTorDaemon) {
+ resolve();
+ } else {
+ Services.obs.addObserver(function obs(subject, topic) {
+ Services.obs.removeObserver(obs, topic);
+ resolve();
+ }, "torconnect:bootstrap-complete");
+ }
+ });
+
// We are ready for initialization as soon as the session file has been
// read from disk and the initial window's delayed startup has finished.
this._promiseReadyForInitialization = Promise.all([
promise,
SessionStartup.onceInitialized,
+ bootstrapPromise,
]);
}
diff --git a/browser/components/torconnect/TorConnectChild.jsm b/browser/components/torconnect/TorConnectChild.jsm
new file mode 100644
index 000000000000..bd6dd549f156
--- /dev/null
+++ b/browser/components/torconnect/TorConnectChild.jsm
@@ -0,0 +1,9 @@
+// Copyright (c) 2021, The Tor Project, Inc.
+
+var EXPORTED_SYMBOLS = ["TorConnectChild"];
+
+const { RemotePageChild } = ChromeUtils.import(
+ "resource://gre/actors/RemotePageChild.jsm"
+);
+
+class TorConnectChild extends RemotePageChild {}
diff --git a/browser/components/torconnect/TorConnectParent.jsm b/browser/components/torconnect/TorConnectParent.jsm
new file mode 100644
index 000000000000..c34fab76ddbb
--- /dev/null
+++ b/browser/components/torconnect/TorConnectParent.jsm
@@ -0,0 +1,126 @@
+// Copyright (c) 2021, The Tor Project, Inc.
+
+var EXPORTED_SYMBOLS = ["TorConnectParent"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+const { TorLauncherUtil } = ChromeUtils.import(
+ "resource://torlauncher/modules/tl-util.jsm"
+);
+
+const { TorConnect } = ChromeUtils.import(
+ "resource:///modules/TorConnect.jsm"
+);
+
+const kTorProcessReadyTopic = "TorProcessIsReady";
+const kTorProcessExitedTopic = "TorProcessExited";
+const kTorProcessDidNotStartTopic = "TorProcessDidNotStart";
+const kTorShowProgressPanelTopic = "TorShowProgressPanel";
+const kTorBootstrapStatusTopic = "TorBootstrapStatus";
+const kTorBootstrapErrorTopic = "TorBootstrapError";
+const kTorLogHasWarnOrErrTopic = "TorLogHasWarnOrErr";
+
+const gActiveTopics = [
+ kTorProcessReadyTopic,
+ kTorProcessExitedTopic,
+ kTorProcessDidNotStartTopic,
+ kTorShowProgressPanelTopic,
+ kTorBootstrapStatusTopic,
+ kTorBootstrapErrorTopic,
+ kTorLogHasWarnOrErrTopic,
+ "torconnect:bootstrap-complete",
+];
+
+const gTorLauncherPrefs = {
+ quickstart: "extensions.torlauncher.quickstart",
+}
+
+class TorConnectParent extends JSWindowActorParent {
+ constructor(...args) {
+ super(...args);
+
+ const self = this;
+ this.gObserver = {
+ observe(aSubject, aTopic, aData) {
+ const obj = aSubject?.wrappedJSObject;
+ if (obj) {
+ obj.handled = true;
+ }
+ self.sendAsyncMessage(aTopic, obj);
+ },
+ };
+
+ for (const topic of gActiveTopics) {
+ Services.obs.addObserver(this.gObserver, topic);
+ }
+
+ this.quickstartObserver = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic === "nsPref:changed" &&
+ aData == gTorLauncherPrefs.quickstart) {
+ self.sendAsyncMessage("TorQuickstartPrefChanged", Services.prefs.getBoolPref(gTorLauncherPrefs.quickstart));
+ }
+ },
+ }
+ Services.prefs.addObserver(gTorLauncherPrefs.quickstart, this.quickstartObserver);
+ }
+
+ willDestroy() {
+ for (const topic of gActiveTopics) {
+ Services.obs.removeObserver(this.gObserver, topic);
+ }
+ }
+
+
+ _OpenTorAdvancedPreferences() {
+ const win = this.browsingContext.top.embedderElement.ownerGlobal;
+ win.openTrustedLinkIn("about:preferences#tor", "tab");
+ }
+
+ _TorCopyLog() {
+ // Copy tor log messages to the system clipboard.
+ const chSvc = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
+ Ci.nsIClipboardHelper
+ );
+ const countObj = { value: 0 };
+ chSvc.copyString(TorProtocolService.getLog(countObj));
+ const count = countObj.value;
+ return TorLauncherUtil.getFormattedLocalizedString(
+ "copiedNLogMessagesShort",
+ [count],
+ 1
+ );
+ }
+
+ receiveMessage(message) {
+ switch (message.name) {
+ case "TorBootstrapErrorOccurred":
+ return TorProtocolService.torBootstrapErrorOccurred();
+ case "TorRetrieveBootstrapStatus":
+ return TorProtocolService.retrieveBootstrapStatus();
+ case "OpenTorAdvancedPreferences":
+ return this._OpenTorAdvancedPreferences();
+ case "GetLocalizedBootstrapStatus":
+ const { status, keyword } = message.data;
+ return TorLauncherUtil.getLocalizedBootstrapStatus(status, keyword);
+ case "TorCopyLog":
+ return this._TorCopyLog();
+ case "TorIsNetworkDisabled":
+ return TorProtocolService.isNetworkDisabled();
+ case "TorStopBootstrap":
+ return TorProtocolService.torStopBootstrap();
+ case "TorConnect":
+ return TorProtocolService.connect();
+ case "GetDirection":
+ return Services.locale.isAppLocaleRTL ? "rtl" : "ltr";
+ case "GetTorStrings":
+ return TorStrings;
+ case "TorLogHasWarnOrErr":
+ return TorProtocolService.torLogHasWarnOrErr();
+ }
+ return undefined;
+ }
+}
diff --git a/browser/components/torconnect/content/aboutTorConnect.css b/browser/components/torconnect/content/aboutTorConnect.css
new file mode 100644
index 000000000000..a70904ca0b28
--- /dev/null
+++ b/browser/components/torconnect/content/aboutTorConnect.css
@@ -0,0 +1,151 @@
+
+/* Copyright (c) 2021, The Tor Project, Inc. */
+
+
+@import url("chrome://browser/skin/error-pages.css");
+
+:root {
+ --onion-opacity: 1;
+ --onion-color: var(--card-outline-color);
+ --onion-radius: 50px;
+}
+
+/* override firefox's default blue focus coloring */
+:focus {
+ outline: none!important;
+ box-shadow: 0 0 0 3px #CD8CED !important;
+ border: 1px #59316B solid !important;
+}
+
+@media (prefers-color-scheme: dark)
+{
+ :focus {
+ box-shadow: 0 0 0 3px #8C689E !important;
+ }
+}
+
+/* override firefox's default blue border on hover */
+input[type="checkbox"]:not(:disabled):hover {
+ border-color: #59316B;
+}
+
+/* fix checkbox visibility when dark mode enabled */
+input[type="checkbox"]:checked {
+ fill: var(--in-content-page-color);
+}
+
+#connectButton {
+ background-color: #7D4698;
+}
+
+#connectButton:hover {
+ background-color: #59316B;
+}
+
+#progressBackground {
+ position:fixed;
+ padding:0;
+ margin:0;
+ top:0;
+ left:0;
+ width: 0%;
+ height: 7px;
+ background-image: linear-gradient(90deg, rgb(20, 218, 221) 0%, rgb(128, 109, 236) 100%);
+ border-radius: 0;
+}
+
+#connectPageContainer {
+ margin-top: 10vh;
+ width: 50%;
+}
+
+#quickstartCheckbox, #quickstartCheckboxLabel {
+ vertical-align: middle;
+}
+
+#copyLogButton {
+ position: relative;
+}
+
+/* mirrors p element spacing */
+#copyLogContainer {
+ margin: 1em 0;
+ height: 1.2em;
+ min-height: 1.2em;
+}
+
+#copyLogLink {
+ position: relative;
+ display: inline-block;
+ color: var(--in-content-link-color);
+}
+
+/* hidden apparently only works if no display is set; who knew? */
+#copyLogLink[hidden="true"] {
+ display: none;
+}
+
+#copyLogLink:hover {
+ cursor:pointer;
+}
+
+/* This div:
+ - is centered over its parent
+ - centers its child
+ - has z-index above parent
+ - ignores mouse events from parent
+*/
+#copyLogTooltip {
+ pointer-events: none;
+ visibility: hidden;
+ display: flex;
+ justify-content: center;
+ white-space: nowrap;
+ width: 0;
+ position: absolute;
+
+ z-index: 1;
+ left: 50%;
+ bottom: calc(100% + 0.25em);
+}
+
+/* tooltip content (any content could go here) */
+#copyLogTooltipText {
+ background-color: #30E60B;
+ color: #003706;
+ border-radius: 2px;
+ padding: 4px;
+ line-height: 13px;
+ font: 11px sans-serif;
+ font-weight: 400;
+}
+
+/* our speech bubble tail */
+#copyLogTooltipText::after {
+ content: "";
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ margin-left: -4px;
+ border-width: 4px;
+ border-style: solid;
+ border-color: #30E60B transparent transparent transparent;
+}
+
+body {
+ padding: 0px !important;
+ justify-content: space-between;
+ background-color: var(--in-content-page-background);
+}
+
+.title {
+ background-image: url("chrome://browser/content/torconnect/onion.svg");
+ -moz-context-properties: fill, fill-opacity;
+ fill-opacity: 1;
+ fill: var(--onion-color);
+}
+
+.title.error {
+ background-image: url("chrome://browser/content/torconnect/onion-slash.svg");
+}
+
diff --git a/browser/components/torconnect/content/aboutTorConnect.js b/browser/components/torconnect/content/aboutTorConnect.js
new file mode 100644
index 000000000000..19fd335ccd13
--- /dev/null
+++ b/browser/components/torconnect/content/aboutTorConnect.js
@@ -0,0 +1,339 @@
+// Copyright (c) 2021, The Tor Project, Inc.
+
+/* eslint-env mozilla/frame-script */
+
+const kTorProcessReadyTopic = "TorProcessIsReady";
+const kTorProcessExitedTopic = "TorProcessExited";
+const kTorProcessDidNotStartTopic = "TorProcessDidNotStart";
+const kTorBootstrapStatusTopic = "TorBootstrapStatus";
+const kTorBootstrapErrorTopic = "TorBootstrapError";
+const kTorLogHasWarnOrErrTopic = "TorLogHasWarnOrErr";
+const kTorQuickstartPrefChanged = "TorQuickstartPrefChanged";
+
+const TorLauncherPrefs = {
+ quickstart: "extensions.torlauncher.quickstart",
+ prompt_at_startup: "extensions.torlauncher.prompt_at_startup",
+}
+
+class AboutTorConnect {
+ log(...args) {
+ console.log(...args);
+ }
+
+ logError(...args) {
+ console.error(...args);
+ }
+
+ logDebug(...args) {
+ console.debug(...args);
+ }
+
+ getElem(id) {
+ return document.getElementById(id);
+ }
+ get elemProgressContent() {
+ return this.getElem("progressContent");
+ }
+ get elemProgressDesc() {
+ return this.getElem("connectShortDescText");
+ }
+ get elemProgressMeter() {
+ return this.getElem("progressBackground");
+ }
+ get elemCopyLogLink() {
+ return this.getElem("copyLogLink");
+ }
+ get elemCopyLogTooltip() {
+ return this.getElem("copyLogTooltip");
+ }
+ get elemCopyLogTooltipText() {
+ return this.getElem("copyLogTooltipText");
+ }
+ get elemQuickstartCheckbox() {
+ return this.getElem("quickstartCheckbox");
+ }
+ get elemQuickstartLabel() {
+ return this.getElem("quickstartCheckboxLabel");
+ }
+ get elemConnectButton() {
+ return this.getElem("connectButton");
+ }
+ get elemAdvancedButton() {
+ return this.getElem("advancedButton");
+ }
+ get elemCancelButton() {
+ return this.getElem("cancelButton");
+ }
+ get elemTextContainer() {
+ return this.getElem("text-container");
+ }
+ get elemTitle() {
+ return this.elemTextContainer.getElementsByClassName("title")[0];
+ }
+
+ static get STATE_INITIAL() {
+ return "STATE_INITIAL";
+ }
+
+ static get STATE_BOOTSTRAPPING() {
+ return "STATE_BOOTSTRAPPING";
+ }
+
+ static get STATE_BOOTSTRAPPED() {
+ return "STATE_BOOTSTRAPPED";
+ }
+
+ static get STATE_BOOTSTRAP_ERROR() {
+ return "STATE_BOOTSTRAP_ERROR";
+ }
+
+ get state() {
+ return this._state;
+ }
+
+ setInitialUI() {
+ this.setTitle(this.torStrings.torConnect.torConnect);
+ this.elemProgressDesc.textContent =
+ this.torStrings.settings.torPreferencesDescription;
+ this.showElem(this.elemConnectButton);
+ this.elemConnectButton.focus();
+ this.showElem(this.elemAdvancedButton);
+ this.hideElem(this.elemCopyLogLink);
+ this.hideElem(this.elemCancelButton);
+ this.hideElem(this.elemProgressContent);
+ this.hideElem(this.elemProgressMeter);
+ this.elemTitle.classList.remove("error");
+ }
+
+ setBootstrappingUI() {
+ this.setTitle(this.torStrings.torConnect.torConnecting);
+ this.hideElem(this.elemConnectButton);
+ this.hideElem(this.elemAdvancedButton);
+ this.hideElem(this.elemCopyLogLink);
+ this.showElem(this.elemCancelButton);
+ this.elemCancelButton.focus();
+ this.showElem(this.elemProgressContent);
+ this.showElem(this.elemProgressMeter);
+ this.elemTitle.classList.remove("error");
+ }
+
+ setBootstrapErrorUI() {
+ this.setTitle(this.torStrings.torConnect.torBootstrapFailed);
+ this.elemConnectButton.textContent = this.torStrings.torConnect.tryAgain;
+ this.showElem(this.elemConnectButton);
+ this.hideElem(this.elemCancelButton);
+ this.showElem(this.elemAdvancedButton);
+ this.elemAdvancedButton.focus();
+ this.showElem(this.elemProgressContent);
+ this.hideElem(this.elemProgressMeter);
+ this.elemTitle.classList.add("error");
+ }
+
+ set state(state) {
+ const oldState = this.state;
+ if (oldState === state) {
+ return;
+ }
+ this._state = state;
+ switch (this.state) {
+ case AboutTorConnect.STATE_INITIAL:
+ this.setInitialUI();
+ break;
+ case AboutTorConnect.STATE_BOOTSTRAPPING:
+ this.setBootstrappingUI();
+ break;
+ case AboutTorConnect.STATE_BOOTSTRAP_ERROR:
+ this.setBootstrapErrorUI();
+ break;
+ case AboutTorConnect.STATE_BOOTSTRAPPED:
+ window.close();
+ break;
+ }
+ }
+
+ async showErrorMessage(aErrorObj) {
+ if (aErrorObj && aErrorObj.message) {
+ this.setTitle(aErrorObj.message);
+ if (aErrorObj.details) {
+ this.elemProgressDesc.textContent = aErrorObj.details;
+ }
+ }
+
+ this.showCopyLog();
+ this.showElem(this.elemConnectButton);
+ }
+
+ showElem(elem) {
+ elem.removeAttribute("hidden");
+ }
+
+ hideElem(elem) {
+ elem.setAttribute("hidden", "true");
+ }
+
+ async connect() {
+ // reset the text to original description
+ // in case we are trying again after an error (clears out error text)
+ this.elemProgressDesc.textContent =
+ this.torStrings.settings.torPreferencesDescription;
+
+ this.state = AboutTorConnect.STATE_BOOTSTRAPPING;
+ const error = await RPMSendQuery("TorConnect");
+ if (error) {
+ if (error.details) {
+ this.showErrorMessage({ message: error.details }, true);
+ this.showSaveSettingsError(error.details);
+ }
+ }
+ }
+
+ showCopyLog() {
+ this.elemCopyLogLink.removeAttribute("hidden");
+ }
+
+ async updateBootstrapProgress(status) {
+ let labelText = await RPMSendQuery("GetLocalizedBootstrapStatus", {
+ status,
+ keyword: "TAG",
+ });
+ let percentComplete = status.PROGRESS ? status.PROGRESS : 0;
+ this.elemProgressMeter.style.width = `${percentComplete}%`;
+
+ if (await RPMSendQuery("TorBootstrapErrorOccurred")) {
+ this.state = AboutTorConnect.STATE_BOOTSTRAP_ERROR;
+ return;
+ } else if (await RPMSendQuery("TorIsNetworkDisabled")) {
+ // If tor network is not connected, let's go to the initial state, even
+ // if bootstrap state is greater than 0.
+ this.state = AboutTorConnect.STATE_INITIAL;
+ return;
+ } else if (percentComplete > 0) {
+ this.state = AboutTorConnect.STATE_BOOTSTRAPPING;
+ }
+
+ // Due to async, status might have changed. Do not override desc if so.
+ if (this.state === AboutTorConnect.STATE_BOOTSTRAPPING) {
+ this.hideElem(this.elemConnectButton);
+ }
+ }
+
+ stopTorBootstrap() {
+ RPMSendAsyncMessage("TorStopBootstrap");
+ }
+
+ setTitle(title) {
+ const titleElement = document.querySelector(".title-text");
+ titleElement.textContent = title;
+ document.title = title;
+ }
+
+ async initElements() {
+ this.elemAdvancedButton.textContent = this.torStrings.torConnect.torConfigure;
+ this.elemAdvancedButton.addEventListener("click", () => {
+ RPMSendAsyncMessage("OpenTorAdvancedPreferences");
+ });
+
+ // sets the text content while keping the child elements intact
+ this.elemCopyLogLink.childNodes[0].nodeValue =
+ this.torStrings.torConnect.copyLog;
+ this.elemCopyLogLink.addEventListener("click", async (event) => {
+ const copiedMessage = await RPMSendQuery("TorCopyLog");
+ aboutTorConnect.elemCopyLogTooltipText.textContent = copiedMessage;
+ aboutTorConnect.elemCopyLogTooltip.style.visibility = "visible";
+
+ // clear previous timeout if one already exists
+ if (aboutTorConnect.copyLogTimeoutId) {
+ clearTimeout(aboutTorConnect.copyLogTimeoutId);
+ }
+
+ // hide tooltip after X ms
+ const TOOLTIP_TIMEOUT = 2000;
+ aboutTorConnect.copyLogTimeoutId = setTimeout(function() {
+ aboutTorConnect.elemCopyLogTooltip.style.visibility = "hidden";
+ aboutTorConnect.copyLogTimeoutId = 0;
+ }, TOOLTIP_TIMEOUT);
+ });
+
+
+ this.elemQuickstartLabel.textContent = this.torStrings.settings.quickstartCheckbox;
+ this.elemQuickstartCheckbox.addEventListener("change", () => {
+ const quickstart = this.elemQuickstartCheckbox.checked;
+ RPMSetBoolPref(TorLauncherPrefs.quickstart, quickstart);
+ });
+ this.elemQuickstartCheckbox.checked = await RPMGetBoolPref(TorLauncherPrefs.quickstart);
+
+ this.elemConnectButton.textContent =
+ this.torStrings.torConnect.torConnectButton;
+ this.elemConnectButton.addEventListener("click", () => {
+ this.connect();
+ });
+
+ this.elemCancelButton.textContent = this.torStrings.torConnect.cancel;
+ this.elemCancelButton.addEventListener("click", () => {
+ this.stopTorBootstrap();
+ });
+ }
+
+ initObservers() {
+ RPMAddMessageListener(kTorBootstrapErrorTopic, ({ data }) => {
+ this.showCopyLog();
+ this.stopTorBootstrap();
+ this.showErrorMessage(data);
+ });
+ RPMAddMessageListener(kTorLogHasWarnOrErrTopic, () => {
+ this.showCopyLog();
+ });
+ RPMAddMessageListener(kTorProcessDidNotStartTopic, ({ data }) => {
+ this.showErrorMessage(data);
+ });
+ RPMAddMessageListener(kTorBootstrapStatusTopic, ({ data }) => {
+ this.updateBootstrapProgress(data);
+ });
+ RPMAddMessageListener(kTorQuickstartPrefChanged, ({ data }) => {
+ // update checkbox with latest quickstart pref value
+ this.elemQuickstartCheckbox.checked = data;
+ });
+ RPMAddMessageListener("torconnect:bootstrap-complete", () => {
+ this.state = AboutTorConnect.STATE_BOOTSTRAPPED;
+ });
+ }
+
+ initKeyboardShortcuts() {
+ document.onkeydown = (evt) => {
+ // unfortunately it looks like we still haven't standardized keycodes to
+ // integers, so we must resort to a string compare here :(
+ // see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code for relevant documentation
+ if (evt.code === "Escape") {
+ this.stopTorBootstrap();
+ }
+ };
+ }
+
+ async init() {
+ this.torStrings = await RPMSendQuery("GetTorStrings");
+ document.documentElement.setAttribute(
+ "dir",
+ await RPMSendQuery("GetDirection")
+ );
+ this.initElements();
+ this.initObservers();
+ this.initKeyboardShortcuts();
+ this.state = AboutTorConnect.STATE_INITIAL;
+
+ // Request the most recent bootstrap status info so that a
+ // TorBootstrapStatus notification is generated as soon as possible.
+ RPMSendAsyncMessage("TorRetrieveBootstrapStatus");
+
+ // quickstart is the user set pref for starting tor automatically
+ // prompt_at_startup will be set to false after successful bootstrap, and true on error
+ // by tor-launcher, so we want to keep the connect screen up when prompt_at_startup is true
+ /// even if quickstart is enabled so user can potentially resolve errors on next launch
+ if (await RPMGetBoolPref(TorLauncherPrefs.quickstart) &&
+ !await RPMGetBoolPref(TorLauncherPrefs.prompt_at_startup)) {
+ this.connect();
+ }
+ }
+}
+
+const aboutTorConnect = new AboutTorConnect();
+aboutTorConnect.init();
diff --git a/browser/components/torconnect/content/aboutTorConnect.xhtml b/browser/components/torconnect/content/aboutTorConnect.xhtml
new file mode 100644
index 000000000000..0a0721afb7db
--- /dev/null
+++ b/browser/components/torconnect/content/aboutTorConnect.xhtml
@@ -0,0 +1,54 @@
+<!-- Copyright (c) 2021, The Tor Project, Inc. -->
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" />
+ <link rel="stylesheet" href="chrome://browser/skin/onionPattern.css" type="text/css" media="all" />
+ <link rel="stylesheet" href="chrome://browser/content/torconnect/aboutTorConnect.css" type="text/css" media="all" />
+ </head>
+ <body>
+ <div id="progressBackground"></div>
+ <div id="connectPageContainer" class="container">
+ <div id="text-container">
+ <div class="title">
+ <h1 class="title-text"/>
+ </div>
+ <div id="connectLongContent">
+ <div id="connectShortDesc">
+ <p id="connectShortDescText" />
+ </div>
+ </div>
+
+ <div id="progressContent" hidden="true">
+ <div class="tbb-header" pack="center">
+ <image class="tbb-logo"/>
+ </div>
+ <div flex="1">
+ <div id="progressDesc"/>
+ </div>
+ </div>
+
+ <div id="copyLogContainer">
+ <span id="copyLogLink" hidden="true">
+ <div id="copyLogTooltip">
+ <span id="copyLogTooltipText"/>
+ </div>
+ </span>
+ </div>
+
+ <div id="quickstartContainer">
+ <input id="quickstartCheckbox" type="checkbox" />
+ <label id="quickstartCheckboxLabel" for="quickstartCheckbox"/>
+ </div>
+
+ <div id="connectButtonContainer" class="button-container">
+ <button id="advancedButton" hidden="true"></button>
+ <button id="cancelButton" hidden="true"></button>
+ <button id="connectButton" class="primary try-again" hidden="true"></button>
+ </div>
+ </div>
+ </div>
+#include ../../../themes/shared/onionPattern.inc.xhtml
+ </body>
+ <script src="chrome://browser/content/torconnect/aboutTorConnect.js"/>
+</html>
diff --git a/browser/components/torconnect/content/onion-slash.svg b/browser/components/torconnect/content/onion-slash.svg
new file mode 100644
index 000000000000..efb09700ec0b
--- /dev/null
+++ b/browser/components/torconnect/content/onion-slash.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+ <g fill-opacity="context-fill-opacity" fill="context-fill">
+ <path d="M3.409559 13.112147C3.409559 13.112147 8.200807 8.103115 8.200807 8.103115C8.200807 8.103115 8.200807 6.516403 8.200807 6.516403C8.620819 6.516403 9.009719 6.703075 9.274171 6.998639C9.274171 6.998639 10.160863 6.080835 10.160863 6.080835C9.663071 5.567487 8.978607 5.256367 8.200807 5.256367C8.200807 5.256367 8.200807 4.400787 8.200807 4.400787C9.196391 4.400787 10.098639 4.805243 10.736435 5.458595C10.736435 5.458595 11.623127 4.540791 11.623127 4.540791C10.751991 3.669655 9.538623 3.125195 8.200807 3.125195C8.200807 3.125195 8.200807 2.269615 8.200807 2.269615C9.756407 2.269615 11.172003 2.907411 12.214255 3.918551C12.214255 3.918551 13.100947 3.000747 13.100947 3.000747C11.825355 1.756267 10.098639 0.994023 8.185251 0.994023C4.311807 0.994023 1.185051 4.120779 1.185051 7.994223C1.185051 10.016503 2.040631 11.836555 3.409559 13.112147C3.409559 13.112147 3.409559 13.112147 3.409559 13.112147"/>
+ <path d="M14.205423 4.416343C14.205423 4.416343 13.287619 5.380815 13.287619 5.380815C13.692075 6.158615 13.909859 7.045307 13.909859 7.994223C13.909859 11.152091 11.358675 13.718831 8.200807 13.718831C8.200807 13.718831 8.200807 12.863251 8.200807 12.863251C10.891995 12.863251 13.069835 10.669855 13.069835 7.978667C13.069835 7.278647 12.929831 6.625295 12.665379 6.018611C12.665379 6.018611 11.685351 7.045307 11.685351 7.045307C11.763131 7.340871 11.809799 7.651991 11.809799 7.963111C11.809799 9.954279 10.207531 11.556547 8.216363 11.572103C8.216363 11.572103 8.216363 10.716523 8.216363 10.716523C9.725295 10.700967 10.954219 9.472043 10.954219 7.963111C10.954219 7.916443 10.954219 7.854219 10.954219 7.807551C10.954219 7.807551 4.887379 14.169955 4.887379 14.169955C5.867407 14.698859 6.987439 14.994423 8.185251 14.994423C12.058695 14.994423 15.185451 11.867667 15.185451 7.994223C15.185451 6.687519 14.827663 5.474151 14.205423 4.416343C14.205423 4.416343 14.205423 4.416343 14.20542
3 4.416343"/>
+ <path d="M1.791735 15.461103C1.402835 15.461103 1.045047 15.212207 0.889487 14.838863C0.733927 14.465519 0.827267 14.014395 1.107271 13.734387C1.107271 13.734387 13.458735 0.822907 13.458735 0.822907C13.847635 0.434007 14.454319 0.449563 14.827663 0.838467C15.201007 1.227367 15.216563 1.865163 14.843223 2.269619C14.843223 2.269619 2.491759 15.181099 2.491759 15.181099C2.289531 15.352215 2.040635 15.461107 1.791739 15.461107C1.791739 15.461107 1.791735 15.461103 1.791735 15.461103"/>
+ </g>
+</svg>
diff --git a/browser/components/torconnect/content/onion.svg b/browser/components/torconnect/content/onion.svg
new file mode 100644
index 000000000000..30cd52ba5c51
--- /dev/null
+++ b/browser/components/torconnect/content/onion.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="M12.0246161,21.8174863 L12.0246161,20.3628098 C16.6324777,20.3495038 20.3634751,16.6108555 20.3634751,11.9996673 C20.3634751,7.38881189 16.6324777,3.65016355 12.0246161,3.63685757 L12.0246161,2.18218107 C17.4358264,2.1958197 21.8178189,6.58546322 21.8178189,11.9996673 C21.8178189,17.4142042 17.4358264,21.8041803 12.0246161,21.8174863 L12.0246161,21.8174863 Z M12.0246161,16.7259522 C14.623607,16.7123136 16.7272828,14.6023175 16.7272828,11.9996673 C16.7272828,9.39734991 14.623607,7.28735377 12.0246161,7.27371516 L12.0246161,5.81937131 C15.4272884,5.8326773 18.1819593,8.59400123 18.1819593,11.9996673 C18.1819593,15.4056661 15.4272884,18.1669901 12.0246161,18.1802961 L12.0246161,16.7259522 Z M12.0246161,9.45556355 C13.4187503,9.46886953 14.5454344,10.6022066 14.5454344,11.9996673 C14.5454344,13.3974608 13.4187503,14.5307978 12.0246161,14.5441038 L12.0246161,9.45556355 Z M0,11.9996673 C0,18.6273771 5.37229031,24 12,24 C18
.6273771,24 24,18.6273771 24,11.9996673 C24,5.37229031 18.6273771,0 12,0 C5.37229031,0 0,5.37229031 0,11.9996673 Z"/>
+</svg>
\ No newline at end of file
diff --git a/browser/components/torconnect/content/torBootstrapUrlbar.js b/browser/components/torconnect/content/torBootstrapUrlbar.js
new file mode 100644
index 000000000000..55a595b2dbab
--- /dev/null
+++ b/browser/components/torconnect/content/torBootstrapUrlbar.js
@@ -0,0 +1,136 @@
+// Copyright (c) 2021, The Tor Project, Inc.
+
+"use strict";
+
+ const TorConnectionStatus = {
+ invalid: -1,
+ offline: 0,
+ connecting: 1,
+ connected: 2,
+ failure: 3,
+ };
+var TorBootstrapUrlbar;
+
+{
+ const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+ );
+ const { TorLauncherUtil } = ChromeUtils.import(
+ "resource://torlauncher/modules/tl-util.jsm"
+ );
+ const { TorStrings } = ChromeUtils.import(
+ "resource:///modules/TorStrings.jsm"
+ );
+
+ const kTorProcessReadyTopic = "TorProcessIsReady";
+ const kTorProcessExitedTopic = "TorProcessExited";
+ const kTorProcessDidNotStartTopic = "TorProcessDidNotStart";
+ const kTorBootstrapStatusTopic = "TorBootstrapStatus";
+ const kTorBootstrapErrorTopic = "TorBootstrapError";
+
+ const gActiveTopics = [
+ kTorProcessReadyTopic,
+ kTorProcessExitedTopic,
+ kTorProcessDidNotStartTopic,
+ kTorBootstrapStatusTopic,
+ kTorBootstrapErrorTopic,
+ ];
+
+ TorBootstrapUrlbar = {
+ _connectionStatus: TorConnectionStatus.invalid,
+ get ConnectionStatus() {
+ return this._connectionStatus;
+ },
+
+ _torConnectBox : null,
+ get TorConnectBox() {
+ if (!this._torConnectBox) {
+ this._torConnectBox =
+ browser.ownerGlobal.document.getElementById("torconnect-box");
+ }
+ return this._torConnectBox;
+ },
+
+ _torConnectLabel : null,
+ get TorConnectLabel() {
+ if (!this._torConnectLabel) {
+ this._torConnectLabel =
+ browser.ownerGlobal.document.getElementById("torconnect-label");
+ }
+ return this._torConnectLabel;
+ },
+
+ _updateConnectionStatus(percentComplete = 0) {
+ if (TorProtocolService.ownsTorDaemon &&
+ !TorLauncherUtil.useLegacyLauncher) {
+ if (TorProtocolService.isNetworkDisabled()) {
+ if (TorProtocolService.torBootstrapErrorOccurred()) {
+ this._connectionStatus = TorConnectionStatus.failure;
+ } else {
+ this._connectionStatus = TorConnectionStatus.offline;
+ }
+ } else if (percentComplete < 100) {
+ this._connectionStatus = TorConnectionStatus.connecting;
+ } else if (percentComplete === 100) {
+ this._connectionStatus = TorConnectionStatus.connected;
+ }
+ }
+ else
+ {
+ this._connectionStatus = TorConnectionStatus.invalid;
+ }
+
+ switch(this._connectionStatus)
+ {
+ case TorConnectionStatus.failure:
+ case TorConnectionStatus.offline:
+ this.TorConnectBox.removeAttribute("hidden");
+ this.TorConnectLabel.textContent = TorStrings.torConnect.offline;
+ gURLBar._inputContainer.setAttribute("torconnect", "offline");
+ break;
+ case TorConnectionStatus.connecting:
+ this.TorConnectLabel.textContent =
+ TorStrings.torConnect.torConnectingConcise;
+ gURLBar._inputContainer.setAttribute("torconnect", "connecting");
+ break;
+ case TorConnectionStatus.connected:
+ this.TorConnectLabel.textContent =
+ TorStrings.torConnect.torConnectedConcise;
+ gURLBar._inputContainer.setAttribute("torconnect", "connected");
+ // hide torconnect box after 5 seconds
+ let self = this;
+ setTimeout(function() {
+ self.TorConnectBox.setAttribute("hidden", "true");
+ }, 5000);
+ break;
+ }
+ },
+
+ observe(aSubject, aTopic, aData) {
+ const obj = aSubject?.wrappedJSObject;
+
+ switch (aTopic) {
+ case kTorProcessReadyTopic:
+ case kTorProcessExitedTopic:
+ case kTorProcessDidNotStartTopic:
+ case kTorBootstrapErrorTopic:
+ this._updateConnectionStatus();
+ break;
+ case kTorBootstrapStatusTopic:
+ let percentComplete = obj.PROGRESS ? obj.PROGRESS : 0;
+ this._updateConnectionStatus(percentComplete);
+ break;
+ }
+ },
+ init() {
+ for (const topic of gActiveTopics) {
+ Services.obs.addObserver(this, topic);
+ }
+ },
+ uninit() {
+ for (const topic of gActiveTopics) {
+ Services.obs.removeObserver(this, topic);
+ }
+ },
+ };
+}
diff --git a/browser/components/torconnect/content/torconnect-urlbar.css b/browser/components/torconnect/content/torconnect-urlbar.css
new file mode 100644
index 000000000000..db2f1069b22b
--- /dev/null
+++ b/browser/components/torconnect/content/torconnect-urlbar.css
@@ -0,0 +1,65 @@
+/*
+ ensure our torconnect button is always visible (same rule as for the bookmark button)
+*/
+hbox.urlbar-page-action#torconnect-box {
+ display: -moz-inline-box!important;
+ margin: 0 6px;
+ height: 28px;
+}
+/* disable the button-like default css */
+hbox.urlbar-page-action#torconnect-box:hover,
+hbox.urlbar-page-action#torconnect-box:active {
+ background-color: inherit!important;
+}
+
+label#torconnect-label {
+ line-height: 28px;
+ margin: 0;
+ opacity: 0.6;
+}
+
+/* set appropriate sizes for the non-standard ui densities */
+:root[uidensity=compact] {
+ hbox.urlbar-page-action#torconnect-box {
+ margin: 0 4px;
+ height: 24px;
+ }
+ label#torconnect-label {
+ line-height: 24px;
+ }
+}
+:root[uidensity=touch] {
+ hbox.urlbar-page-action#torconnect-box {
+ margin: 0 7px;
+ height: 30px;
+ }
+ label#torconnect-label {
+ line-height: 30px;
+ }
+}
+
+/* hide when hidden attribute is set */
+hbox.urlbar-page-action#torconnect-box[hidden="true"],
+/* hide when user is typing in URL bar */
+#urlbar[usertyping] > #urlbar-input-container > #page-action-buttons > #torconnect-box {
+ display: none!important;
+}
+
+/* hide urlbar's placeholder text when not connectd to tor */
+hbox#urlbar-input-container[torconnect="offline"] input#urlbar-input::placeholder,
+hbox#urlbar-input-container[torconnect="connecting"] input#urlbar-input::placeholder {
+ opacity: 0;
+}
+
+/* hide search suggestions when not connected to tor */
+hbox#urlbar-input-container[torconnect="offline"] + vbox.urlbarView,
+hbox#urlbar-input-container[torconnect="connecting"] + vbox.urlbarView {
+ display: none!important;
+}
+
+/* hide search icon when we are not connected to tor */
+hbox#urlbar-input-container[torconnect="offline"] > #identity-box[pageproxystate="invalid"] > #identity-icon,
+hbox#urlbar-input-container[torconnect="connecting"] > #identity-box[pageproxystate="invalid"] > #identity-icon
+{
+ display: none!important;
+}
diff --git a/browser/components/torconnect/content/torconnect-urlbar.inc.xhtml b/browser/components/torconnect/content/torconnect-urlbar.inc.xhtml
new file mode 100644
index 000000000000..bdf9d8f0df00
--- /dev/null
+++ b/browser/components/torconnect/content/torconnect-urlbar.inc.xhtml
@@ -0,0 +1,11 @@
+# Copyright (c) 2021, The Tor Project, Inc.
+
+<hbox id="torconnect-box"
+ class="urlbar-icon-wrapper urlbar-page-action"
+ role="status"
+ hidden="true">
+ <image id="torconnect-button" role="presentation"/>
+ <hbox id="torconnect-container">
+ <label id="torconnect-label"/>
+ </hbox>
+</hbox>
\ No newline at end of file
diff --git a/browser/components/torconnect/jar.mn b/browser/components/torconnect/jar.mn
new file mode 100644
index 000000000000..ed8a4de299b2
--- /dev/null
+++ b/browser/components/torconnect/jar.mn
@@ -0,0 +1,7 @@
+browser.jar:
+ content/browser/torconnect/torBootstrapUrlbar.js (content/torBootstrapUrlbar.js)
+ content/browser/torconnect/aboutTorConnect.css (content/aboutTorConnect.css)
+* content/browser/torconnect/aboutTorConnect.xhtml (content/aboutTorConnect.xhtml)
+ content/browser/torconnect/aboutTorConnect.js (content/aboutTorConnect.js)
+ content/browser/torconnect/onion.svg (content/onion.svg)
+ content/browser/torconnect/onion-slash.svg (content/onion-slash.svg)
diff --git a/browser/components/torconnect/moz.build b/browser/components/torconnect/moz.build
new file mode 100644
index 000000000000..eb29c31a4243
--- /dev/null
+++ b/browser/components/torconnect/moz.build
@@ -0,0 +1,6 @@
+JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_JS_MODULES += [
+ 'TorConnectChild.jsm',
+ 'TorConnectParent.jsm',
+]
diff --git a/browser/components/torpreferences/content/torPane.js b/browser/components/torpreferences/content/torPane.js
index 49054b5dac6a..01609ddda090 100644
--- a/browser/components/torpreferences/content/torPane.js
+++ b/browser/components/torpreferences/content/torPane.js
@@ -1,5 +1,7 @@
"use strict";
+/* global Services */
+
const { TorProtocolService } = ChromeUtils.import(
"resource:///modules/TorProtocolService.jsm"
);
@@ -51,6 +53,10 @@ const { parsePort, parseBridgeStrings, parsePortList } = ChromeUtils.import(
"chrome://browser/content/torpreferences/parseFunctions.jsm"
);
+const TorLauncherPrefs = {
+ quickstart: "extensions.torlauncher.quickstart",
+}
+
/*
Tor Pane
@@ -62,11 +68,21 @@ const gTorPane = (function() {
category: {
title: "label#torPreferences-labelCategory",
},
+ messageBox: {
+ box: "div#torPreferences-connectMessageBox",
+ message: "td#torPreferences-connectMessageBox-message",
+ button: "button#torPreferences-connectMessageBox-button",
+ },
torPreferences: {
header: "h1#torPreferences-header",
description: "span#torPreferences-description",
learnMore: "label#torPreferences-learnMore",
},
+ quickstart: {
+ header: "h2#torPreferences-quickstart-header",
+ description: "span#torPreferences-quickstart-description",
+ enableQuickstartCheckbox: "checkbox#torPreferences-quickstart-toggle",
+ },
bridges: {
header: "h2#torPreferences-bridges-header",
description: "span#torPreferences-bridges-description",
@@ -112,6 +128,10 @@ const gTorPane = (function() {
let retval = {
// cached frequently accessed DOM elements
+ _messageBox: null,
+ _messageBoxMessage: null,
+ _messageBoxButton: null,
+ _enableQuickstartCheckbox: null,
_useBridgeCheckbox: null,
_bridgeSelectionRadiogroup: null,
_builtinBridgeOption: null,
@@ -161,6 +181,43 @@ const gTorPane = (function() {
let prefpane = document.getElementById("mainPrefPane");
+ // 'Connect to Tor' Message Bar
+
+ this._messageBox = prefpane.querySelector(selectors.messageBox.box);
+ this._messageBoxMessage = prefpane.querySelector(selectors.messageBox.message);
+ this._messageBoxButton = prefpane.querySelector(selectors.messageBox.button);
+ // wire up connect button
+ this._messageBoxButton.addEventListener("click", () => {
+ TorProtocolService.connect();
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ // switch to existing about:torconnect tab or create a new one
+ win.switchToTabHavingURI("about:torconnect", true);
+ });
+
+ let populateMessagebox = () => {
+ if (TorProtocolService.shouldShowTorConnect()) {
+ // set messagebox style and text
+ if (TorProtocolService.torBootstrapErrorOccurred()) {
+ this._messageBox.className = "error";
+ this._messageBoxMessage.innerText = TorStrings.torConnect.tryAgainMessage;
+ this._messageBoxButton.innerText = TorStrings.torConnect.tryAgain;
+ } else {
+ this._messageBox.className = "warning";
+ this._messageBoxMessage.innerText = TorStrings.torConnect.connectMessage;
+ this._messageBoxButton.innerText = TorStrings.torConnect.torConnectButton;
+ }
+ } else {
+ this._messageBox.className = "hidden";
+ this._messageBoxMessage.innerText = "";
+ this._messageBoxButton.innerText = "";
+ }
+ }
+ populateMessagebox();
+ // update the messagebox whenever we come back to the page
+ window.addEventListener("focus", val => {
+ populateMessagebox();
+ });
+
// Heading
prefpane.querySelector(selectors.torPreferences.header).innerText =
TorStrings.settings.torPreferencesHeading;
@@ -177,6 +234,26 @@ const gTorPane = (function() {
);
}
+ // Quickstart
+ prefpane.querySelector(selectors.quickstart.header).innerText =
+ TorStrings.settings.quickstartHeading;
+ prefpane.querySelector(selectors.quickstart.description).textContent =
+ TorStrings.settings.quickstartDescription;
+
+ this._enableQuickstartCheckbox = prefpane.querySelector(
+ selectors.quickstart.enableQuickstartCheckbox
+ );
+ this._enableQuickstartCheckbox.setAttribute(
+ "label",
+ TorStrings.settings.quickstartCheckbox
+ );
+ this._enableQuickstartCheckbox.addEventListener("command", e => {
+ const checked = this._enableQuickstartCheckbox.checked;
+ Services.prefs.setBoolPref(TorLauncherPrefs.quickstart, checked);
+ });
+ this._enableQuickstartCheckbox.checked = Services.prefs.getBoolPref(TorLauncherPrefs.quickstart);
+ Services.prefs.addObserver(TorLauncherPrefs.quickstart, this);
+
// Bridge setup
prefpane.querySelector(selectors.bridges.header).innerText =
TorStrings.settings.bridgesHeading;
@@ -537,6 +614,15 @@ const gTorPane = (function() {
// Callbacks
//
+ // callback for when the quickstart pref changes
+ observe(subject, topic, data) {
+ if (topic != "nsPref:changed") return;
+ if (data === TorLauncherPrefs.quickstart) {
+ this._enableQuickstartCheckbox.checked =
+ Services.prefs.getBoolPref(TorLauncherPrefs.quickstart);
+ }
+ },
+
// callback when using bridges toggled
onToggleBridge(enabled) {
this._useBridgeCheckbox.checked = enabled;
diff --git a/browser/components/torpreferences/content/torPane.xhtml b/browser/components/torpreferences/content/torPane.xhtml
index 3c966b2b3726..7c8071f2cf10 100644
--- a/browser/components/torpreferences/content/torPane.xhtml
+++ b/browser/components/torpreferences/content/torPane.xhtml
@@ -3,6 +3,29 @@
<script type="application/javascript"
src="chrome://browser/content/torpreferences/torPane.js"/>
<html:template id="template-paneTor">
+
+<!-- Tor Connect Message Box -->
+<groupbox data-category="paneTor" hidden="true">
+ <html:div id="torPreferences-connectMessageBox"
+ class="subcategory"
+ data-category="paneTor"
+ hidden="true">
+ <html:table >
+ <html:tr>
+ <html:td>
+ <html:div id="torPreferences-connectMessageBox-icon"/>
+ </html:td>
+ <html:td id="torPreferences-connectMessageBox-message">
+ </html:td>
+ <html:td>
+ <html:button id="torPreferences-connectMessageBox-button">
+ </html:button>
+ </html:td>
+ </html:tr>
+ </html:table>
+ </html:div>
+</groupbox>
+
<hbox id="torPreferencesCategory"
class="subcategory"
data-category="paneTor"
@@ -18,6 +41,17 @@
</description>
</groupbox>
+<!-- Quickstart -->
+<groupbox id="torPreferences-quickstart-group"
+ data-category="paneTor"
+ hidden="true">
+ <html:h2 id="torPreferences-quickstart-header"/>
+ <description flex="1">
+ <html:span id="torPreferences-quickstart-description"/>
+ </description>
+ <checkbox id="torPreferences-quickstart-toggle"/>
+</groupbox>
+
<!-- Bridges -->
<groupbox id="torPreferences-bridges-group"
data-category="paneTor"
diff --git a/browser/components/torpreferences/content/torPreferences.css b/browser/components/torpreferences/content/torPreferences.css
index 4dac2c457823..f125936dac74 100644
--- a/browser/components/torpreferences/content/torPreferences.css
+++ b/browser/components/torpreferences/content/torPreferences.css
@@ -2,6 +2,127 @@
list-style-image: url("chrome://browser/content/torpreferences/torPreferencesIcon.svg");
}
+/* Connect Message Box */
+
+#torPreferences-connectMessageBox {
+ display: block;
+ position: relative;
+
+ width: auto;
+ min-height: 32px;
+ border-radius: 4px;
+ padding: 4px;
+}
+
+#torPreferences-connectMessageBox.hidden {
+ display: none;
+}
+
+#torPreferences-connectMessageBox.error {
+ background-color: var(--red-60);
+ color: white;
+}
+
+#torPreferences-connectMessageBox.warning {
+ background-color: var(--yellow-50);
+ color: var(--yellow-90);
+}
+
+#torPreferences-connectMessageBox table {
+ border-collapse: collapse;
+ width: 100%;
+}
+
+#torPreferences-connectMessageBox td {
+ vertical-align: top;
+ padding: 0px;
+}
+
+#torPreferences-connectMessageBox td:first-child {
+ width: 24px;
+}
+
+#torPreferences-connectMessageBox-icon {
+ display: block;
+ width: 16px;
+ height: 16px;
+ padding: 4px;
+
+ mask-repeat: no-repeat !important;
+ mask-size: 16px !important;
+ mask-position: 4px 4px !important;
+}
+
+#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-icon
+{
+ mask: url("chrome://browser/skin/onion-slash.svg");
+ background-color: white;
+}
+
+#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-icon
+{
+ mask: url("chrome://global/skin/icons/warning.svg");
+ background-color: black;
+}
+
+#torPreferences-connectMessageBox-message {
+ display: block;
+ line-height: 16px;
+ font-size: 13px;
+ margin-right: 8px;
+ padding-left: 4px!important;
+ padding-top: 4px!important;
+}
+
+#torPreferences-connectMessageBox-button {
+ display: block;
+ width: auto;
+ height: 24px;
+ line-height: 24px;
+ min-height: 24px;
+ max-height: 24px;
+ margin: 0px;
+
+ border-radius: 2px;
+ border: 0;
+ padding-left: 8px;
+ padding-right: 8px;
+ margin-left: auto;
+ margin-right: 0px;
+
+ font-size: 11px;
+ font-weight: 400;
+ white-space: nowrap;
+}
+
+#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-button {
+ background-color: var(--red-70);
+}
+
+#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-button:hover {
+ background-color: var(--red-80);
+}
+
+#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-button:active {
+ background-color: var(--red-90);
+}
+
+#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-button {
+ background-color: var(--yellow-60);
+}
+
+#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-button:hover {
+ background-color: var(--yellow-70);
+ color: white!important;
+}
+
+#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-button:active {
+ background-color: var(--yellow-80);
+ color: white!important;
+}
+
+/* Advanced Settings */
+
#torPreferences-advanced-grid {
display: grid;
grid-template-columns: auto 1fr;
diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm
index 13b1279105f2..f727c386701c 100644
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -10,6 +10,33 @@ const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+
+// in certain scenarios we want user input uris to open in a new tab if they do so from the
+// about:torconnect tab
+function maybeUpdateOpenLocationForTorConnect(openUILinkWhere, currentURI, destinationURI) {
+ try {
+ // only open in new tab if:
+ if (// user is navigating away from about:torconnect
+ currentURI === "about:torconnect" &&
+ // we are trying to open in same tab
+ openUILinkWhere === "current" &&
+ // only if user still has not bootstrapped
+ TorProtocolService.shouldShowTorConnect() &&
+ // and user is not just navigating to about:torconnect
+ destinationURI !== "about:torconnect") {
+ return "tab";
+ }
+ } catch (e) {
+ // swallow exception and fall through returning original so we don't accidentally break
+ // anything if an exception is thrown
+ }
+
+ return openUILinkWhere;
+};
+
XPCOMUtils.defineLazyModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.jsm",
BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
@@ -1832,6 +1859,10 @@ class UrlbarInput {
// area when the current tab is re-selected.
browser.focus();
+ openUILinkWhere = maybeUpdateOpenLocationForTorConnect(
+ openUILinkWhere,
+ this.window.gBrowser.currentURI.asciiSpec,
+ url);
if (openUILinkWhere != "current") {
this.handleRevert();
}
diff --git a/browser/modules/TorConnect.jsm b/browser/modules/TorConnect.jsm
new file mode 100644
index 000000000000..3125c84558db
--- /dev/null
+++ b/browser/modules/TorConnect.jsm
@@ -0,0 +1,62 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["TorConnect"];
+
+const { Services } = ChromeUtils.import(
+ "resource://gre/modules/Services.jsm"
+);
+
+const { BrowserWindowTracker } = ChromeUtils.import(
+ "resource:///modules/BrowserWindowTracker.jsm"
+);
+
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+
+// TODO: move the bootstrap state management out of each of the individual
+// about:torconnect pages and stick it here
+var TorConnect = (() => {
+ let retval = {
+ init : function() {
+ let topics = [
+ "TorBootstrapStatus",
+ ];
+
+ for(const topic of topics) {
+ Services.obs.addObserver(this, topic);
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ switch(topic) {
+ case "TorBootstrapStatus":
+ const obj = subject?.wrappedJSObject;
+ if (obj?.PROGRESS === 100) {
+ // open home page(s) in new tabs
+ const win = BrowserWindowTracker.getTopWindow()
+ const urls = Services.prefs.getStringPref("browser.startup.homepage").split('|');
+
+ let location="tab";
+ for(const url of urls) {
+ win.openTrustedLinkIn(url, location);
+ // open subsequent tabs behind first tab
+ location = "tabshifted";
+ }
+
+ Services.obs.notifyObservers(null, "torconnect:bootstrap-complete");
+ }
+ break;
+ default:
+ // ignore
+ break;
+ }
+ },
+
+ shouldShowTorConnect : function() {
+ return TorProtocolService.shouldShowTorConnect();
+ },
+ };
+ retval.init();
+ return retval;
+})(); /* TorConnect */
\ No newline at end of file
diff --git a/browser/modules/TorProcessService.jsm b/browser/modules/TorProcessService.jsm
new file mode 100644
index 000000000000..201e331b2806
--- /dev/null
+++ b/browser/modules/TorProcessService.jsm
@@ -0,0 +1,12 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["TorProcessService"];
+
+var TorProcessService = {
+ get isBootstrapDone() {
+ const svc = Cc["@torproject.org/torlauncher-process-service;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ return svc.mIsBootstrapDone;
+ },
+};
diff --git a/browser/modules/TorProtocolService.jsm b/browser/modules/TorProtocolService.jsm
index b4e6ed9a3253..fc7f2c884aa2 100644
--- a/browser/modules/TorProtocolService.jsm
+++ b/browser/modules/TorProtocolService.jsm
@@ -1,3 +1,5 @@
+// Copyright (c) 2021, The Tor Project, Inc.
+
"use strict";
var EXPORTED_SYMBOLS = ["TorProtocolService"];
@@ -11,6 +13,10 @@ var TorProtocolService = {
Ci.nsISupports
).wrappedJSObject,
+ _tlproc: Cc["@torproject.org/torlauncher-process-service;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject,
+
// maintain a map of tor settings set by Tor Browser so that we don't
// repeatedly set the same key/values over and over
// this map contains string keys to primitive or array values
@@ -196,11 +202,11 @@ var TorProtocolService = {
// writes current tor settings to disk
flushSettings() {
- this._tlps.TorSendCommand("SAVECONF");
+ this.sendCommand("SAVECONF");
},
- getLog() {
- let countObj = { value: 0 };
+ getLog(countObj) {
+ countObj = countObj || { value: 0 };
let torLog = this._tlps.TorGetLog(countObj);
return torLog;
},
@@ -209,4 +215,116 @@ var TorProtocolService = {
get ownsTorDaemon() {
return TorLauncherUtil.shouldStartAndOwnTor;
},
+
+ // Assumes `ownsTorDaemon` is true
+ isNetworkDisabled() {
+ const reply = TorProtocolService._tlps.TorGetConfBool(
+ "DisableNetwork",
+ true
+ );
+ if (TorProtocolService._tlps.TorCommandSucceeded(reply)) {
+ return reply.retVal;
+ }
+ return true;
+ },
+
+ enableNetwork() {
+ let settings = {};
+ settings.DisableNetwork = false;
+ let errorObject = {};
+ if (!this._tlps.TorSetConfWithReply(settings, errorObject)) {
+ throw new Error(errorObject.details);
+ }
+ },
+
+ sendCommand(cmd) {
+ return this._tlps.TorSendCommand(cmd);
+ },
+
+ retrieveBootstrapStatus() {
+ return this._tlps.TorRetrieveBootstrapStatus();
+ },
+
+ _GetSaveSettingsErrorMessage(aDetails) {
+ try {
+ return TorLauncherUtil.getSaveSettingsErrorMessage(aDetails);
+ } catch (e) {
+ console.log("GetSaveSettingsErrorMessage error", e);
+ return "Unexpected Error";
+ }
+ },
+
+ setConfWithReply(settings) {
+ let result = false;
+ const error = {};
+ try {
+ result = this._tlps.TorSetConfWithReply(settings, error);
+ } catch (e) {
+ console.log("TorSetConfWithReply error", e);
+ error.details = this._GetSaveSettingsErrorMessage(e.message);
+ }
+ return { result, error };
+ },
+
+ isBootstrapDone() {
+ return this._tlproc.mIsBootstrapDone;
+ },
+
+ clearBootstrapError() {
+ return this._tlproc.TorClearBootstrapError();
+ },
+
+ shouldShowTorConnect() {
+ return (
+ this.ownsTorDaemon &&
+ !TorLauncherUtil.useLegacyLauncher &&
+ (this.isNetworkDisabled() || !this.isBootstrapDone())
+ );
+ },
+
+ torBootstrapErrorOccurred() {
+ return this._tlproc.TorBootstrapErrorOccurred;
+ },
+
+ // Resolves to null if ok, or an error otherwise
+ connect() {
+ const kTorConfKeyDisableNetwork = "DisableNetwork";
+ const settings = {};
+ settings[kTorConfKeyDisableNetwork] = false;
+ const { result, error } = this.setConfWithReply(settings);
+ if (!result) {
+ return error;
+ }
+ try {
+ this.sendCommand("SAVECONF");
+ this.clearBootstrapError();
+ this.retrieveBootstrapStatus();
+ } catch (e) {
+ return error;
+ }
+ return null;
+ },
+
+ torLogHasWarnOrErr() {
+ return this._tlps.TorLogHasWarnOrErr;
+ },
+
+ torStopBootstrap() {
+ // Tell tor to disable use of the network; this should stop the bootstrap
+ // process.
+ const kErrorPrefix = "Setting DisableNetwork=1 failed: ";
+ try {
+ let settings = {};
+ settings.DisableNetwork = true;
+ const { result, error } = this.setConfWithReply(settings);
+ if (!result) {
+ console.log(
+ `Error stopping bootstrap ${kErrorPrefix} ${error.details}`
+ );
+ }
+ } catch (e) {
+ console.log(`Error stopping bootstrap ${kErrorPrefix} ${e}`);
+ }
+ this.retrieveBootstrapStatus();
+ },
};
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm
index 1e08b168e4af..c086d0fe3117 100644
--- a/browser/modules/TorStrings.jsm
+++ b/browser/modules/TorStrings.jsm
@@ -257,6 +257,9 @@ var TorStrings = {
"Tor Browser routes your traffic over the Tor Network, run by thousands of volunteers around the world."
),
learnMore: getString("torPreferences.learnMore", "Learn More"),
+ quickstartHeading: getString("torPreferences.quickstart", "Quickstart"),
+ quickstartDescription: getString("torPreferences.quickstartDescription", "Quickstart allows Tor Browser to connect automatically."),
+ quickstartCheckbox : getString("torPreferences.quickstartCheckbox", "Always connect automatically"),
bridgesHeading: getString("torPreferences.bridges", "Bridges"),
bridgesDescription: getString(
"torPreferences.bridgesDescription",
@@ -364,6 +367,78 @@ var TorStrings = {
return retval;
})() /* Tor Network Settings Strings */,
+ torConnect: (() => {
+ const tsbNetwork = new TorDTDStringBundle(
+ ["chrome://torlauncher/locale/network-settings.dtd"],
+ ""
+ );
+ const tsbLauncher = new TorPropertyStringBundle(
+ "chrome://torlauncher/locale/torlauncher.properties",
+ "torlauncher."
+ );
+ const tsbCommon = new TorPropertyStringBundle(
+ "chrome://global/locale/commonDialogs.properties",
+ ""
+ );
+
+ const getStringNet = tsbNetwork.getString.bind(tsbNetwork);
+ const getStringLauncher = tsbLauncher.getString.bind(tsbLauncher);
+ const getStringCommon = tsbCommon.getString.bind(tsbCommon);
+
+ return {
+ torConnect: getStringNet(
+ "torsettings.wizard.title.default",
+ "Connect to Tor"
+ ),
+
+ torConnecting: getStringNet(
+ "torsettings.wizard.title.connecting",
+ "Establishing a Connection"
+ ),
+
+ torConnectingConcise: getStringNet(
+ "torConnect.connectingConcise",
+ "Connecting…"
+ ),
+
+ torBootstrapFailed: getStringLauncher(
+ "tor_bootstrap_failed",
+ "Tor failed to establish a Tor network connection."
+ ),
+
+ torConfigure: getStringNet(
+ "torsettings.wizard.title.configure",
+ "Tor Network Settings"
+ ),
+
+ copyLog: getStringNet(
+ "torConnect.copyLog",
+ "Copy Tor Logs"
+ ),
+
+ torConnectButton: getStringNet("torSettings.connect", "Connect"),
+
+ cancel: getStringCommon("Cancel", "Cancel"),
+
+ torConnected: getStringLauncher(
+ "torlauncher.bootstrapStatus.done",
+ "Connected to the Tor network"
+ ),
+
+ torConnectedConcise: getStringLauncher(
+ "torConnect.connectedConcise",
+ "Connected"
+ ),
+
+ tryAgain: getStringNet("torConnect.tryAgain", "Try connecting again"),
+ offline: getStringNet("torConnect.offline", "Offline"),
+
+ // tor connect strings for message box in about:preferences#tor
+ connectMessage: getStringNet("torConnect.connectMessage", "Changes to Tor Settings will not take effect until you connect to the Tor Network"),
+ tryAgainMessage: getStringNet("torConnect.tryAgainMessage", "Tor Browser has failed to establish a connection to the Tor Network"),
+ };
+ })(),
+
/*
Tor Onion Services Strings, e.g., for the authentication prompt.
*/
diff --git a/browser/modules/moz.build b/browser/modules/moz.build
index 5fb78d1c07a8..7f091e0e7711 100644
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -155,6 +155,8 @@ EXTRA_JS_MODULES += [
'TabUnloader.jsm',
'ThemeVariableMap.jsm',
'TopSiteAttribution.jsm',
+ 'TorConnect.jsm',
+ 'TorProcessService.jsm',
'TorProtocolService.jsm',
'TorStrings.jsm',
'TransientPrefs.jsm',
diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn
index e4a3c8d2d41c..d38e1001282b 100644
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -8,6 +8,7 @@
# to the location of the actual manifest.
skin/classic/browser/aboutNetError.css (../shared/aboutNetError.css)
+ skin/classic/browser/onionPattern.css (../shared/onionPattern.css)
skin/classic/browser/blockedSite.css (../shared/blockedSite.css)
skin/classic/browser/error-pages.css (../shared/error-pages.css)
skin/classic/browser/aboutRestartRequired.css (../shared/aboutRestartRequired.css)
diff --git a/browser/themes/shared/onionPattern.css b/browser/themes/shared/onionPattern.css
new file mode 100644
index 000000000000..c605a4b4f59e
--- /dev/null
+++ b/browser/themes/shared/onionPattern.css
@@ -0,0 +1,124 @@
+/* Onion pattern */
+
+:root {
+ --sqrt3: 1.73205080757;
+}
+
+.onion-pattern-container {
+ opacity: var(--onion-opacity, 1);
+ flex: auto; /* grow to consume remaining space on the page */
+ display: flex;
+ margin: 0 auto;
+ width: 100%;
+ height: calc((2 + var(--sqrt3)) * var(--onion-radius, 50px)); /* room for 2 rows of circles */
+ max-height: calc((2 + var(--sqrt3)) * var(--onion-radius, 50px));
+ direction: ltr;
+}
+
+.onion-pattern-crop {
+ display: flex;
+ justify-content: center;
+ overflow-x: hidden;
+ pointer-events: none; /* for some reason, elements with overflow-x: hidden set become focusable */
+
+ margin: 0 auto;
+}
+
+/* Centers horizontally within the root container*/
+.onion-pattern-column {
+ width: calc(40 * var(--onion-radius, 50px)); /* room for 20 circles in a row */
+ height: calc((2 + var(--sqrt3)) * var(--onion-radius, 50px)); /* room for 2 rows of circles */
+ flex-shrink: 0;
+ overflow-x: hidden; /* clip extra circles on the sides */
+ pointer-events: none; /* for some reason, elements with overflow-x: hidden set become focusable */
+}
+
+.onion-pattern-row {
+ width: calc(40 * var(--onion-radius, 50px)); /* room for 20 circles in a row */
+ display: flex;
+ flex-direction: row;
+ position: relative;
+}
+
+.onion-pattern-offset-row {
+ left: calc(-1 * var(--onion-radius, 50px));
+ margin-top: calc((var(--sqrt3) - 2.0) * var(--onion-radius, 50px));
+}
+
+/* With borders, circles are 100x100 pixels*/
+.circle {
+ position: relative;
+ min-width: calc(2 * var(--onion-radius, 50px));
+ min-height: calc(2 * var(--onion-radius, 50px));
+ border-radius: 50%;
+ box-sizing: border-box;
+}
+
+.inner {
+ position: absolute;
+ box-sizing: border-box;
+ border-radius: 50%;
+}
+
+.inner:nth-child(1){
+ width: 100%;
+ height: 100%;
+}
+
+.inner:nth-child(2){
+ transform: translate(20%, 20%);
+ width: calc(100% * 5/7);
+ height: calc(100% * 5/7);
+}
+
+.inner:nth-child(3){
+ transform: translate(calc(100% * 2/3), calc(100% * 2/3));
+ width: calc(100% * 3/7);
+ height: calc(100% * 3/7);
+}
+
+.inner:nth-child(4){
+ transform: translate(300%, 300%);
+ width: calc(100% * 1/7);
+ height: calc(100% * 1/7);
+}
+
+.solid {
+ background-color: var(--onion-color, #000);
+}
+
+.border {
+ border: 4px solid var(--onion-color, #000);
+}
+
+.dashed {
+ border: 4px dashed var(--onion-color, #000);
+}
+
+.dotted {
+ border: 4px dotted var(--onion-color, #000);
+}
+
+.bold {
+ border: 8px solid var(--onion-color, #000);
+}
+
+.top-half {
+ width: calc(2 * var(--onion-radius, 50px));
+ height: var(--onion-radius, 50px);
+ border-radius: var(--onion-radius, 50px) var(--onion-radius, 50px) 0 0;
+ box-sizing: border-box;
+}
+
+.bottom-half {
+ width: calc(2 * var(--onion-radius, 50px));
+ height: var(--onion-radius, 50px);
+ border-radius: 0 0 var(--onion-radius, 50px) var(--onion-radius, 50px);
+ box-sizing: border-box;
+}
+
+.scaler {
+ position: absolute;
+ left:0;
+ bottom:0;
+}
\ No newline at end of file
diff --git a/browser/themes/shared/onionPattern.inc.xhtml b/browser/themes/shared/onionPattern.inc.xhtml
new file mode 100644
index 000000000000..6bbde93684a2
--- /dev/null
+++ b/browser/themes/shared/onionPattern.inc.xhtml
@@ -0,0 +1,210 @@
+<!--
+ - The abstract onion pattern begins here. There are two
+ - "onion-pattern-row" elements, each containing 14 circles. The width
+ - of "onion-pattern-row" is fixed at a value that is wide enough so the
+ - circles are not distorted by the flex-based layout. The parent
+ - "onion-pattern-container" element has overflow-x: hidden and is designed
+ - to expand to the width of the page, until it reaches a maximum width
+ - that can accommodate all 14 circles. Since the rows are wider than
+ - most browser windows, typically the two rows of onions will fill the
+ - bottom of the page. On really wide pages, the onions are centered at
+ - the bottom of the page.
+-->
+
+<div class="onion-pattern-container">
+ <!-- for some reason, these two elements are focusable, seems related to
+ - flex css somehow; disable their tabindex to fix
+ -->
+ <div class="onion-pattern-crop" tabindex="-1">
+ <div class="onion-pattern-column" tabindex="-1">
+ <div class="onion-pattern-row">
+ <div class="circle solid"></div>
+
+ <div class="circle dashed"></div>
+
+ <div class="circle">
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ </div>
+
+ <div class="circle">
+ <div class="bottom-half solid"></div>
+ <div class="bottom-half dotted"></div>
+ </div>
+
+ <div class="circle border"></div>
+
+ <div class="circle">
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ </div>
+
+ <div class="circle solid"></div>
+
+ <div class="circle">
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ </div>
+
+ <div class="circle bold"></div>
+
+ <div class="circle">
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ </div>
+
+ <div class="circle bold"></div>
+
+ <div class="circle">
+ <div class="bottom-half solid"></div>
+ <div class="bottom-half solid"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ </div>
+
+ <div class="circle dotted"></div>
+
+ <div class="circle">
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ </div>
+
+ <div class="circle solid"></div>
+
+ <div class="circle">
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ </div>
+
+ <div class="circle bold"></div>
+
+ <div class="circle dashed"></div>
+ </div>
+
+ <div class="onion-pattern-row onion-pattern-offset-row">
+ <div class="circle">
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ </div>
+
+ <div class="circle bold"></div>
+
+ <div class="circle solid"></div>
+
+ <div class="circle">
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ </div>
+
+ <div class="circle">
+ <div class="top-half solid"></div>
+ <div class="top-half solid"></div>
+ </div>
+
+ <div class="circle border"></div>
+
+ <div class="circle dotted"></div>
+
+ <div class="circle">
+ <div class="top-half border"></div>
+ <div class="top-half dashed"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ </div>
+
+ <div class="circle">
+ <div class="top-half dotted"></div>
+ <div class="top-half solid"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ </div>
+
+ <div class="circle dotted"></div>
+
+ <div class="circle bold"></div>
+
+ <div class="circle solid"></div>
+
+ <div class="circle">
+ <div class="top-half solid"></div>
+ <div class="top-half dotted"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ </div>
+
+ <div class="circle dotted"></div>
+
+ <div class="circle">
+ <div class="top-half solid"></div>
+ <div class="top-half solid"></div>
+ </div>
+
+ <div class="circle dotted"></div>
+ </div>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/browser/themes/shared/urlbar-searchbar.inc.css b/browser/themes/shared/urlbar-searchbar.inc.css
index d3cc6bf7f024..297dbcdf444d 100644
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -826,3 +826,5 @@
}
%include ../../components/onionservices/content/onionlocation-urlbar.css
+%include ../../components/torconnect/content/torconnect-urlbar.css
+
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
index afc872569519..e74851a6672c 100644
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -16387,9 +16387,56 @@ void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
StylePrefersColorScheme Document::PrefersColorScheme(
IgnoreRFP aIgnoreRFP) const {
+
+ // tor-browser#27476
+ // should this document ignore resist finger-printing settings with regards to
+ // setting the color scheme
+ // currently only enabled for about:torconnect but we could expand to other non-
+ // SystemPrincipal pages if we wish
+ const auto documentUsesPreferredColorScheme = [](auto const* constDocument) -> bool {
+ if (auto* document = const_cast<Document*>(constDocument); document != nullptr) {
+ auto uri = document->GetDocBaseURI();
+
+ // try and extract out our prepath and filepath portions of the uri to C-strings
+ nsAutoCString prePathStr, filePathStr;
+ if(NS_FAILED(uri->GetPrePath(prePathStr)) ||
+ NS_FAILED(uri->GetFilePath(filePathStr))) {
+ return false;
+ }
+
+ // stick them in string view for easy comparisons
+ std::string_view prePath(prePathStr.get(), prePathStr.Length()),
+ filePath(filePathStr.get(), filePathStr.Length());
+
+ // these about URIs will have the user's preferred color scheme exposed to them
+ // we can place other URIs here in the future if we wish
+ // see nsIURI.idl for URI part definitions
+ constexpr struct {
+ std::string_view prePath;
+ std::string_view filePath;
+ } allowedURIs[] = {
+ { "about:", "torconnect" },
+ };
+
+ // check each uri in the allow list against this document's uri
+ // verify the prepath and the file path match
+ for(auto const& uri : allowedURIs) {
+ if (prePath == uri.prePath &&
+ filePath == uri.filePath) {
+ // positive match means we can apply dark-mode to the page
+ return true;
+ }
+ }
+ }
+
+ // do not allow if no match or other error
+ return false;
+ };
+
if (aIgnoreRFP == IgnoreRFP::No &&
- nsContentUtils::ShouldResistFingerprinting(this)) {
- return StylePrefersColorScheme::Light;
+ nsContentUtils::ShouldResistFingerprinting(this) &&
+ !documentUsesPreferredColorScheme(this)) {
+ return StylePrefersColorScheme::Light;
}
if (nsPresContext* pc = GetPresContext()) {
diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp
index abe1e56d9714..759060f131ff 100644
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -6082,6 +6082,8 @@ void nsGlobalWindowOuter::CloseOuter(bool aTrustedCaller) {
NS_ENSURE_SUCCESS_VOID(rv);
if (!StringBeginsWith(url, NS_LITERAL_STRING("about:neterror")) &&
+ // we want about:torconnect pages to be able to close themselves after bootstrap
+ !StringBeginsWith(url, NS_LITERAL_STRING("about:torconnect")) &&
!HadOriginalOpener() && !aTrustedCaller) {
bool allowClose =
mAllowScriptsToClose ||
diff --git a/toolkit/modules/AsyncPrefs.jsm b/toolkit/modules/AsyncPrefs.jsm
index aca86556cd5e..b81ff5e22b9b 100644
--- a/toolkit/modules/AsyncPrefs.jsm
+++ b/toolkit/modules/AsyncPrefs.jsm
@@ -18,6 +18,8 @@ const kAllowedPrefs = new Set([
"testing.allowed-prefs.some-char-pref",
"testing.allowed-prefs.some-int-pref",
+ "extensions.torlauncher.quickstart",
+
"narrate.rate",
"narrate.voice",
diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm
index eceaa7c857de..0927391c2ba7 100644
--- a/toolkit/modules/RemotePageAccessManager.jsm
+++ b/toolkit/modules/RemotePageAccessManager.jsm
@@ -96,6 +96,7 @@ let RemotePageAccessManager = {
RPMPrefIsLocked: ["security.tls.version.min"],
RPMAddToHistogram: ["*"],
RPMGetTorStrings: ["*"],
+ RPMSendQuery: ["ShouldShowTorConnect"],
},
"about:newinstall": {
RPMGetUpdateChannel: ["*"],
@@ -179,6 +180,31 @@ let RemotePageAccessManager = {
RPMAddMessageListener: ["*"],
RPMRemoveMessageListener: ["*"],
},
+ "about:torconnect": {
+ RPMAddMessageListener: ["*"],
+ RPMSendAsyncMessage: [
+ "OpenTorAdvancedPreferences",
+ "TorRetrieveBootstrapStatus",
+ "TorStopBootstrap",
+ ],
+ RPMSendQuery: [
+ "GetDirection",
+ "GetLocalizedBootstrapStatus",
+ "GetTorStrings",
+ "TorBootstrapErrorOccurred",
+ "TorConnect",
+ "TorCopyLog",
+ "TorIsNetworkDisabled",
+ "TorLogHasWarnOrErr",
+ ],
+ RPMGetBoolPref: [
+ "extensions.torlauncher.quickstart",
+ "extensions.torlauncher.prompt_at_startup",
+ ],
+ RPMSetBoolPref: [
+ "extensions.torlauncher.quickstart",
+ ],
+ },
},
/**
diff --git a/toolkit/mozapps/update/UpdateService.jsm b/toolkit/mozapps/update/UpdateService.jsm
index 1fb397373151..79881cba42d6 100644
--- a/toolkit/mozapps/update/UpdateService.jsm
+++ b/toolkit/mozapps/update/UpdateService.jsm
@@ -12,6 +12,17 @@ const { AppConstants } = ChromeUtils.import(
const { AUSTLMY } = ChromeUtils.import(
"resource://gre/modules/UpdateTelemetry.jsm"
);
+
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+
+function _shouldRegisterBootstrapObserver(errorCode) {
+ return errorCode == PROXY_SERVER_CONNECTION_REFUSED &&
+ !TorProtocolService.isBootstrapDone() &&
+ TorProtocolService.ownsTorDaemon;
+};
+
const {
Bits,
BitsRequest,
@@ -201,6 +212,7 @@ const INVALID_UPDATER_STATUS_CODE = 99;
// Custom update error codes
const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
const NETWORK_ERROR_OFFLINE = 111;
+const PROXY_SERVER_CONNECTION_REFUSED = 2152398920;
// Error codes should be < 1000. Errors above 1000 represent http status codes
const HTTP_ERROR_OFFSET = 1000;
@@ -2220,6 +2232,9 @@ UpdateService.prototype = {
case "network:offline-status-changed":
this._offlineStatusChanged(data);
break;
+ case "torconnect:bootstrap-complete":
+ this._bootstrapComplete();
+ break;
case "nsPref:changed":
if (data == PREF_APP_UPDATE_LOG || data == PREF_APP_UPDATE_LOG_FILE) {
gLogEnabled; // Assigning this before it is lazy-loaded is an error.
@@ -2640,6 +2655,35 @@ UpdateService.prototype = {
this._attemptResume();
},
+ _registerBootstrapObserver: function AUS__registerBootstrapObserver() {
+ if (this._registeredBootstrapObserver) {
+ LOG(
+ "UpdateService:_registerBootstrapObserver - observer already registered"
+ );
+ return;
+ }
+
+ LOG(
+ "UpdateService:_registerBootstrapObserver - waiting for tor bootstrap to " +
+ "be complete, then forcing another check"
+ );
+
+ Services.obs.addObserver(this, "torconnect:bootstrap-complete");
+ this._registeredBootstrapObserver = true;
+ },
+
+ _bootstrapComplete: function AUS__bootstrapComplete() {
+ Services.obs.removeObserver(this, "torconnect:bootstrap-complete");
+ this._registeredBootstrapObserver = false;
+
+ LOG(
+ "UpdateService:_bootstrapComplete - bootstrapping complete, forcing " +
+ "another background check"
+ );
+
+ this._attemptResume();
+ },
+
onCheckComplete: function AUS_onCheckComplete(request, updates) {
this._selectAndInstallUpdate(updates);
},
@@ -2659,6 +2703,11 @@ UpdateService.prototype = {
AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_OFFLINE);
}
return;
+ } else if (_shouldRegisterBootstrapObserver(update.errorCode)) {
+ // Register boostrap observer to try again, but only when we own the
+ // tor process.
+ this._registerBootstrapObserver();
+ return;
}
// Send the error code to telemetry
@@ -5189,6 +5238,7 @@ Downloader.prototype = {
var state = this._patch.state;
var shouldShowPrompt = false;
var shouldRegisterOnlineObserver = false;
+ var shouldRegisterBootstrapObserver = false;
var shouldRetrySoon = false;
var deleteActiveUpdate = false;
var retryTimeout = Services.prefs.getIntPref(
@@ -5266,7 +5316,18 @@ Downloader.prototype = {
);
shouldRegisterOnlineObserver = true;
deleteActiveUpdate = false;
-
+ } else if(_shouldRegisterBootstrapObserver(status)) {
+ // Register a bootstrap observer to try again.
+ // The bootstrap observer will continue the incremental download by
+ // calling downloadUpdate on the active update which continues
+ // downloading the file from where it was.
+ LOG("Downloader:onStopRequest - not bootstrapped, register bootstrap observer: true");
+ AUSTLMY.pingDownloadCode(
+ this.isCompleteUpdate,
+ AUSTLMY.DWNLD_RETRY_OFFLINE
+ );
+ shouldRegisterBootstrapObserver = true;
+ deleteActiveUpdate = false;
// Each of NS_ERROR_NET_TIMEOUT, ERROR_CONNECTION_REFUSED,
// NS_ERROR_NET_RESET and NS_ERROR_DOCUMENT_NOT_CACHED can be returned
// when disconnecting the internet while a download of a MAR is in
@@ -5384,7 +5445,7 @@ Downloader.prototype = {
// Only notify listeners about the stopped state if we
// aren't handling an internal retry.
- if (!shouldRetrySoon && !shouldRegisterOnlineObserver) {
+ if (!shouldRetrySoon && !shouldRegisterOnlineObserver && !shouldRegisterBootstrapObserver) {
// Make shallow copy in case listeners remove themselves when called.
var listeners = this._listeners.concat();
var listenerCount = listeners.length;
@@ -5532,6 +5593,9 @@ Downloader.prototype = {
if (shouldRegisterOnlineObserver) {
LOG("Downloader:onStopRequest - Registering online observer");
this.updateService._registerOnlineObserver();
+ } else if (shouldRegisterBootstrapObserver) {
+ LOG("Downloader:onStopRequest - Registering bootstrap observer");
+ this.updateService._registerBootstrapObserver();
} else if (shouldRetrySoon) {
LOG("Downloader:onStopRequest - Retrying soon");
this.updateService._consecutiveSocketErrors++;
diff --git a/toolkit/themes/shared/in-content/info-pages.inc.css b/toolkit/themes/shared/in-content/info-pages.inc.css
index 6943a3340e35..5b3c911a5aab 100644
--- a/toolkit/themes/shared/in-content/info-pages.inc.css
+++ b/toolkit/themes/shared/in-content/info-pages.inc.css
@@ -41,10 +41,11 @@ body.wide-container {
background-image: url("chrome://global/skin/icons/info.svg");
background-position: left 0;
background-repeat: no-repeat;
- background-size: 1.6em;
- margin-inline-start: -2.3em;
- padding-inline-start: 2.3em;
- font-size: 2.2em;
+ background-size: 3.0em;
+ margin-inline-start: -4.5em;
+ padding-inline-start: 4.5em;
+ margin-bottom: -2.0em;
+ font-size: 1.5em;
-moz-context-properties: fill;
fill: currentColor;
}
@@ -56,7 +57,10 @@ body.wide-container {
.title-text {
font-size: inherit;
- padding-bottom: 0.4em;
+ padding-bottom: 2.0em !important;
+ line-height: 1.0em;
+ font-weight: bold;
+ vertical-align: top;
}
@media (max-width: 970px) {
@@ -68,6 +72,7 @@ body.wide-container {
.title-text {
padding-top: 0;
+ vertical-align: middle !important;
}
}
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js
index 2ff107b553b2..f8fa83574df7 100644
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js
@@ -70,6 +70,10 @@ function getGlobalScriptIncludes(scriptPath) {
let match = line.match(globalScriptsRegExp);
if (match) {
let sourceFile = match[1]
+ .replace(
+ "chrome://browser/content/torconnect/",
+ "browser/components/torconnect/content/"
+ )
.replace(
"chrome://browser/content/search/",
"browser/components/search/content/"
1
0

[tor-browser/tor-browser-78.14.0esr-10.5-1] Adding issue template for bugs.
by sysrqb@torproject.org 04 Sep '21
by sysrqb@torproject.org 04 Sep '21
04 Sep '21
commit 68d835c90f177c046511b52588a37a63de2b9a29
Author: Gaba <gaba(a)torproject.org>
Date: Mon Jun 28 11:44:16 2021 -0700
Adding issue template for bugs.
---
.gitlab/issue_templates/UXBug.md | 29 +++++++++++++++++++++++++++++
.gitlab/issue_templates/bug.md | 32 ++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+)
diff --git a/.gitlab/issue_templates/UXBug.md b/.gitlab/issue_templates/UXBug.md
new file mode 100644
index 000000000000..8e7cb2a5e163
--- /dev/null
+++ b/.gitlab/issue_templates/UXBug.md
@@ -0,0 +1,29 @@
+<!--
+* Use this issue template for reporting a new UX bug.
+-->
+
+### Summary
+**Summarize the bug encountered concisely.**
+
+
+### Steps to reproduce:
+**How one can reproduce the issue - this is very important.**
+
+1. Step 1
+2. Step 2
+3. ...
+
+### What is the current bug behavior?
+**What actually happens.**
+
+
+### What is the expected behavior?
+**What you want to see instead**
+
+
+
+## Relevant logs and/or screenshots
+**Do you have screenshots? Attach them to this ticket please.**
+
+/label ~tor-ux ~needs-investigation ~bug
+/assign @nah
diff --git a/.gitlab/issue_templates/bug.md b/.gitlab/issue_templates/bug.md
new file mode 100644
index 000000000000..6ce85a4864be
--- /dev/null
+++ b/.gitlab/issue_templates/bug.md
@@ -0,0 +1,32 @@
+<!--
+* Use this issue template for reporting a new bug.
+-->
+
+### Summary
+**Summarize the bug encountered concisely.**
+
+
+### Steps to reproduce:
+**How one can reproduce the issue - this is very important.**
+
+1. Step 1
+2. Step 2
+3. ...
+
+### What is the current bug behavior?
+**What actually happens.**
+
+
+### What is the expected behavior?
+**What you want to see instead**
+
+
+
+### Environment
+**Which operating system are you using? For example: Debian GNU/Linux 10.1, Windows 10, Ubuntu Xenial, FreeBSD 12.2, etc.**
+**Which installation method did you use? Distribution package (apt, pkg, homebrew), from source tarball, from Git, etc.**
+
+### Relevant logs and/or screenshots
+
+
+/label ~bug
1
0

[tor-browser/tor-browser-78.14.0esr-10.5-1] Bug 33342: Avoid disconnect search addon error after removal.
by sysrqb@torproject.org 04 Sep '21
by sysrqb@torproject.org 04 Sep '21
04 Sep '21
commit 7715beeba975f67557edab91bc677d2d60101dbc
Author: Alex Catarineu <acat(a)torproject.org>
Date: Fri Mar 13 18:19:30 2020 +0100
Bug 33342: Avoid disconnect search addon error after removal.
We removed the addon in #32767, but it was still being loaded
from addonStartup.json.lz4 and throwing an error on startup
because its resource: location is not available anymore.
---
toolkit/mozapps/extensions/internal/XPIProvider.jsm | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
index 5e467fb4f14c..794c206fb453 100644
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -970,6 +970,12 @@ var BuiltInLocation = new (class _BuiltInLocation extends XPIStateLocation {
get enumerable() {
return false;
}
+
+ restore(saved) {
+ super.restore(saved);
+ // Bug 33342: avoid restoring disconnect addon from addonStartup.json.lz4.
+ this.removeAddon("disconnect(a)search.mozilla.org");
+ }
})();
/**
1
0