lists.torproject.org
Sign In Sign Up
Manage this list Sign In Sign Up

Keyboard Shortcuts

Thread View

  • j: Next unread message
  • k: Previous unread message
  • j a: Jump to all threads
  • j l: Jump to MailingList overview

tbb-commits

Thread Start a new thread
Threads by month
  • ----- 2026 -----
  • July
  • June
  • May
  • April
  • March
  • February
  • January
  • ----- 2025 -----
  • December
  • November
  • October
  • September
  • August
  • July
  • 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
tbb-commits@lists.torproject.org

  • 1 participants
  • 20633 discussions
[tor-browser/tor-browser-80.0b2-10.0-1] Orfox: Centralized proxy applied to AbstractCommunicator and BaseResources.
by gk@torproject.org 17 Aug '20

17 Aug '20
commit 917b2e2fa2406df3f3966ee7157a979944f813c2 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
0 0
[tor-browser/tor-browser-80.0b2-10.0-1] Bug 32418: Allow updates to be disabled via an enterprise policy.
by gk@torproject.org 17 Aug '20

17 Aug '20
commit d35a2270667807151921b641b6f64a1314f8fef8 Author: Kathy Brade <brade(a)pearlcrescent.com> Date: Thu Apr 16 17:07:09 2020 -0400 Bug 32418: Allow updates to be disabled via an enterprise policy. Restrict the Enterprise Policies mechanism to only consult a policies.json file (avoiding the Windows Registry and macOS's file system attributes). Add a few disabledByPolicy() checks to the update service to avoid extraneous (and potentially confusing) log messages when updates are disabled by policy. Sample content for distribution/policies.json: { "policies": { "DisableAppUpdate": true } } On Linux, avoid reading policies from /etc/firefox/policies/policies.json --- .../enterprisepolicies/EnterprisePoliciesParent.jsm | 14 ++++++++++++-- toolkit/components/enterprisepolicies/moz.build | 3 +++ toolkit/mozapps/update/UpdateService.jsm | 20 ++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.jsm b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.jsm index 95128ad9779f..760a3027cd5f 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.jsm +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.jsm @@ -4,6 +4,10 @@ var EXPORTED_SYMBOLS = ["EnterprisePoliciesManager"]; +// To ensure that policies intended for Firefox or another browser will not +// be used, Tor Browser only looks for policies in ${InstallDir}/distribution +#define AVOID_SYSTEM_POLICIES MOZ_PROXY_BYPASS_PROTECTION + const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); @@ -13,9 +17,11 @@ const { AppConstants } = ChromeUtils.import( ); XPCOMUtils.defineLazyModuleGetters(this, { +#ifndef AVOID_SYSTEM_POLICIES WindowsGPOParser: "resource://gre/modules/policies/WindowsGPOParser.jsm", macOSPoliciesParser: "resource://gre/modules/policies/macOSPoliciesParser.jsm", +#endif Policies: "resource:///modules/policies/Policies.jsm", JsonSchemaValidator: "resource://gre/modules/components-utils/JsonSchemaValidator.jsm", @@ -112,6 +118,7 @@ EnterprisePoliciesManager.prototype = { _chooseProvider() { let provider = null; +#ifndef AVOID_SYSTEM_POLICIES if (AppConstants.platform == "win") { provider = new WindowsGPOPoliciesProvider(); } else if (AppConstants.platform == "macosx") { @@ -120,6 +127,7 @@ EnterprisePoliciesManager.prototype = { if (provider && provider.hasPolicies) { return provider; } +#endif provider = new JSONPoliciesProvider(); if (provider.hasPolicies) { @@ -470,7 +478,7 @@ class JSONPoliciesProvider { _getConfigurationFile() { let configFile = null; - +#ifndef AVOID_SYSTEM_POLICIES if (AppConstants.platform == "linux") { let systemConfigFile = Cc["@mozilla.org/file/local;1"].createInstance( Ci.nsIFile @@ -483,7 +491,7 @@ class JSONPoliciesProvider { return systemConfigFile; } } - +#endif try { let perUserPath = Services.prefs.getBoolPref(PREF_PER_USER_DIR, false); if (perUserPath) { @@ -564,6 +572,7 @@ class JSONPoliciesProvider { } } +#ifndef AVOID_SYSTEM_POLICIES class WindowsGPOPoliciesProvider { constructor() { this._policies = null; @@ -629,3 +638,4 @@ class macOSPoliciesProvider { return this._failed; } } +#endif diff --git a/toolkit/components/enterprisepolicies/moz.build b/toolkit/components/enterprisepolicies/moz.build index 284089594b2f..b0485aade0e8 100644 --- a/toolkit/components/enterprisepolicies/moz.build +++ b/toolkit/components/enterprisepolicies/moz.build @@ -21,6 +21,9 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != "android": EXTRA_JS_MODULES += [ 'EnterprisePolicies.jsm', 'EnterprisePoliciesContent.jsm', + ] + + EXTRA_PP_JS_MODULES += [ 'EnterprisePoliciesParent.jsm', ] diff --git a/toolkit/mozapps/update/UpdateService.jsm b/toolkit/mozapps/update/UpdateService.jsm index e9953032e1a2..335e65728e53 100644 --- a/toolkit/mozapps/update/UpdateService.jsm +++ b/toolkit/mozapps/update/UpdateService.jsm @@ -2754,6 +2754,10 @@ UpdateService.prototype = { _checkForBackgroundUpdates: function AUS__checkForBackgroundUpdates( isNotify ) { + if (this.disabledByPolicy) { + return; + } + this._isNotify = isNotify; // Histogram IDs: @@ -3254,6 +3258,14 @@ UpdateService.prototype = { * See nsIUpdateService.idl */ get canApplyUpdates() { + if (this.disabledByPolicy) { + LOG( + "UpdateService.canApplyUpdates - unable to apply updates, " + + "the option has been disabled by the administrator." + ); + return false; + } + return getCanApplyUpdates() && hasUpdateMutex(); }, @@ -3261,6 +3273,14 @@ UpdateService.prototype = { * See nsIUpdateService.idl */ get canStageUpdates() { + if (this.disabledByPolicy) { + LOG( + "UpdateService.canStageUpdates - unable to stage updates, " + + "the option has been disabled by the administrator." + ); + return false; + } + return getCanStageUpdates(); },
1 0
0 0
[tor-browser/tor-browser-80.0b2-10.0-1] Bug 28125 - Prevent non-Necko network connections
by gk@torproject.org 17 Aug '20

17 Aug '20
commit ecedf3eb5c883896d307cc6853d92b62f0c8ff24 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, 4 insertions(+), 92 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 6e5095b0a4c9..a585e283ed4e 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 @@ -46,6 +46,7 @@ import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; import org.mozilla.gecko.util.ProxySelector; + /** * An {@link HttpDataSource} that uses Android's {@link HttpURLConnection}. * @@ -516,50 +517,8 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou boolean followRedirects, Map<String, String> requestParameters) 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); - - Map<String, String> requestHeaders = new HashMap<>(); - if (defaultRequestProperties != null) { - requestHeaders.putAll(defaultRequestProperties.getSnapshot()); - } - requestHeaders.putAll(requestProperties.getSnapshot()); - requestHeaders.putAll(requestParameters); - - for (Map.Entry<String, String> property : requestHeaders.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); - connection.setRequestProperty("Accept-Encoding", allowGzip ? "gzip" : "identity"); - connection.setInstanceFollowRedirects(followRedirects); - connection.setDoOutput(httpBody != null); - connection.setRequestMethod(DataSpec.getStringForHttpMethod(httpMethod)); - - if (httpBody != null) { - connection.setFixedLengthStreamingMode(httpBody.length); - connection.connect(); - OutputStream os = connection.getOutputStream(); - os.write(httpBody); - os.close(); - } else { - connection.connect(); - } - return connection; + Log.i(TAG, "This is Tor Browser. Skipping."); + throw new IOException(); } /** Creates an {@link HttpURLConnection} that is connected with the {@code url}. */
1 0
0 0
[tor-browser/tor-browser-80.0b2-10.0-1] Bug 21952: Implement Onion-Location
by gk@torproject.org 17 Aug '20

17 Aug '20
commit fa5d6c0b60500c13a324d71f814e7cef4cfb3a51 Author: Alex Catarineu <acat(a)torproject.org> Date: Thu Mar 5 22:16:39 2020 +0100 Bug 21952: Implement Onion-Location Whenever a valid Onion-Location HTTP header (or corresponding HTML <meta> http-equiv attribute) is found in a document load, we either redirect to it (if the user opted-in via preference) or notify the presence of an onionsite alternative with a badge in the urlbar. --- browser/base/content/browser.js | 12 ++ browser/base/content/browser.xhtml | 3 + browser/components/BrowserGlue.jsm | 13 ++ .../onionservices/OnionLocationChild.jsm | 39 +++++ .../onionservices/OnionLocationParent.jsm | 168 +++++++++++++++++++++ .../content/onionlocation-notification-icons.css | 5 + .../onionservices/content/onionlocation-urlbar.css | 27 ++++ .../content/onionlocation-urlbar.inc.xhtml | 10 ++ .../onionservices/content/onionlocation.svg | 3 + .../content/onionlocationPreferences.inc.xhtml | 11 ++ .../content/onionlocationPreferences.js | 31 ++++ browser/components/onionservices/jar.mn | 2 + browser/components/onionservices/moz.build | 2 + browser/components/preferences/privacy.inc.xhtml | 2 + browser/components/preferences/privacy.js | 17 +++ browser/themes/shared/notification-icons.inc.css | 2 + browser/themes/shared/urlbar-searchbar.inc.css | 2 + dom/base/Document.cpp | 34 ++++- dom/base/Document.h | 2 + dom/webidl/Document.webidl | 9 ++ modules/libpref/init/StaticPrefList.yaml | 5 + xpcom/ds/StaticAtoms.py | 1 + 22 files changed, 399 insertions(+), 1 deletion(-) diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 9a7005889aa3..1b7aad309dd4 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -45,6 +45,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { NetUtil: "resource://gre/modules/NetUtil.jsm", NewTabUtils: "resource://gre/modules/NewTabUtils.jsm", OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.jsm", + OnionLocationParent: "resource:///modules/OnionLocationParent.jsm", PageActions: "resource:///modules/PageActions.jsm", PageThumbs: "resource://gre/modules/PageThumbs.jsm", PanelMultiView: "resource:///modules/PanelMultiView.jsm", @@ -5337,6 +5338,7 @@ var XULBrowserWindow = { Services.obs.notifyObservers(null, "touchbar-location-change", location); UpdateBackForwardCommands(gBrowser.webNavigation); AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser); + OnionLocationParent.updateOnionLocationBadge(gBrowser.selectedBrowser); if (!gMultiProcessBrowser) { // Bug 1108553 - Cannot rotate images with e10s @@ -5863,6 +5865,16 @@ const AccessibilityRefreshBlocker = { var TabsProgressListener = { onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) { + // Clear OnionLocation UI + if ( + aStateFlags & Ci.nsIWebProgressListener.STATE_START && + aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK && + aRequest && + aWebProgress.isTopLevel + ) { + OnionLocationParent.onStateChange(aBrowser); + } + // Collect telemetry data about tab load times. if ( aWebProgress.isTopLevel && diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml index 07b84e5e4473..372d0aa9a7c3 100644 --- a/browser/base/content/browser.xhtml +++ b/browser/base/content/browser.xhtml @@ -1676,6 +1676,9 @@ onclick="FullZoom.reset();" tooltip="dynamic-shortcut-tooltip" hidden="true"/> + +#include ../../components/onionservices/content/onionlocation-urlbar.inc.xhtml + <box id="pageActionSeparator" class="urlbar-page-action"/> <image id="pageActionButton" class="urlbar-icon urlbar-page-action" diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index c6ad31c3d3f6..f0b5a2f47ce1 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -429,6 +429,19 @@ let JSWINDOWACTORS = { allFrames: true, }, + OnionLocation: { + parent: { + moduleURI: "resource:///modules/OnionLocationParent.jsm", + }, + child: { + moduleURI: "resource:///modules/OnionLocationChild.jsm", + events: { + pageshow: { mozSystemGroup: true }, + }, + }, + messageManagerGroups: ["browsers"], + }, + PageInfo: { child: { moduleURI: "resource:///actors/PageInfoChild.jsm", diff --git a/browser/components/onionservices/OnionLocationChild.jsm b/browser/components/onionservices/OnionLocationChild.jsm new file mode 100644 index 000000000000..9e00054ac56c --- /dev/null +++ b/browser/components/onionservices/OnionLocationChild.jsm @@ -0,0 +1,39 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +var EXPORTED_SYMBOLS = ["OnionLocationChild"]; + +class OnionLocationChild extends JSWindowActorChild { + handleEvent(event) { + this.onPageShow(event); + } + + onPageShow(event) { + if (event.target != this.document) { + return; + } + const onionLocationURI = this.document.onionLocationURI; + if (onionLocationURI) { + this.sendAsyncMessage("OnionLocation:Set"); + } + } + + receiveMessage(aMessage) { + if (aMessage.name == "OnionLocation:Refresh") { + const doc = this.document; + const docShell = this.docShell; + const onionLocationURI = doc.onionLocationURI; + const refreshURI = docShell.QueryInterface(Ci.nsIRefreshURI); + if (onionLocationURI && refreshURI) { + refreshURI.refreshURI( + onionLocationURI, + doc.nodePrincipal, + 0, + false, + true + ); + } + } + } +} diff --git a/browser/components/onionservices/OnionLocationParent.jsm b/browser/components/onionservices/OnionLocationParent.jsm new file mode 100644 index 000000000000..f6250e554862 --- /dev/null +++ b/browser/components/onionservices/OnionLocationParent.jsm @@ -0,0 +1,168 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +var EXPORTED_SYMBOLS = ["OnionLocationParent"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + +// Prefs +const NOTIFICATION_PREF = "privacy.prioritizeonions.showNotification"; +const PRIORITIZE_ONIONS_PREF = "privacy.prioritizeonions.enabled"; + +// Element IDs +const ONIONLOCATION_BOX_ID = "onion-location-box"; +const ONIONLOCATION_BUTTON_ID = "onion-location-button"; +const ONIONLOCATION_LABEL_ID = "onion-label"; + +// Notification IDs +const NOTIFICATION_ID = "onion-location"; +const NOTIFICATION_ANCHOR_ID = "onionlocation"; + +// Strings +const STRING_ONION_AVAILABLE = TorStrings.onionLocation.onionAvailable; +const NOTIFICATION_CANCEL_LABEL = TorStrings.onionLocation.notNow; +const NOTIFICATION_CANCEL_ACCESSKEY = TorStrings.onionLocation.notNowAccessKey; +const NOTIFICATION_OK_LABEL = TorStrings.onionLocation.alwaysPrioritize; +const NOTIFICATION_OK_ACCESSKEY = + TorStrings.onionLocation.alwaysPrioritizeAccessKey; +const NOTIFICATION_TITLE = TorStrings.onionLocation.tryThis; +const NOTIFICATION_DESCRIPTION = TorStrings.onionLocation.description; +const NOTIFICATION_LEARN_MORE_URL = TorStrings.onionLocation.learnMoreURL; + +class OnionLocationParent extends JSWindowActorParent { + // Listeners are added in BrowserGlue.jsm + receiveMessage(aMsg) { + switch (aMsg.name) { + case "OnionLocation:Set": + let browser = this.browsingContext.embedderElement; + OnionLocationParent.setOnionLocation(browser); + break; + } + } + + static buttonClick(event) { + if (event.button !== 0) { + return; + } + const win = event.target.ownerGlobal; + if (win.gBrowser) { + const browser = win.gBrowser.selectedBrowser; + OnionLocationParent.redirect(browser); + } + } + + static redirect(browser) { + let windowGlobal = browser.browsingContext.currentWindowGlobal; + let actor = windowGlobal.getActor("OnionLocation"); + if (actor) { + actor.sendAsyncMessage("OnionLocation:Refresh", {}); + OnionLocationParent.setDisabled(browser); + } + } + + static onStateChange(browser) { + delete browser._onionLocation; + OnionLocationParent.hideNotification(browser); + } + + static setOnionLocation(browser) { + browser._onionLocation = true; + let tabBrowser = browser.getTabBrowser(); + if (tabBrowser && browser === tabBrowser.selectedBrowser) { + OnionLocationParent.updateOnionLocationBadge(browser); + } + } + + static hideNotification(browser) { + const win = browser.ownerGlobal; + if (browser._onionLocationPrompt) { + win.PopupNotifications.remove(browser._onionLocationPrompt); + } + } + + static showNotification(browser) { + const mustShow = Services.prefs.getBoolPref(NOTIFICATION_PREF, true); + if (!mustShow) { + return; + } + + const win = browser.ownerGlobal; + Services.prefs.setBoolPref(NOTIFICATION_PREF, false); + + const mainAction = { + label: NOTIFICATION_OK_LABEL, + accessKey: NOTIFICATION_OK_ACCESSKEY, + callback() { + Services.prefs.setBoolPref(PRIORITIZE_ONIONS_PREF, true); + OnionLocationParent.redirect(browser); + win.openPreferences("privacy-onionservices"); + }, + }; + + const cancelAction = { + label: NOTIFICATION_CANCEL_LABEL, + accessKey: NOTIFICATION_CANCEL_ACCESSKEY, + callback: () => {}, + }; + + const options = { + autofocus: true, + persistent: true, + removeOnDismissal: false, + eventCallback(aTopic) { + if (aTopic === "removed") { + delete browser._onionLocationPrompt; + delete browser.onionpopupnotificationanchor; + } + }, + learnMoreURL: NOTIFICATION_LEARN_MORE_URL, + displayURI: { + hostPort: NOTIFICATION_TITLE, // This is hacky, but allows us to have a title without extra markup/css. + }, + hideClose: true, + popupIconClass: "onionlocation-notification-icon", + }; + + // A hacky way of setting the popup anchor outside the usual url bar icon box + // onionlocationpopupnotificationanchor comes from `${ANCHOR_ID}popupnotificationanchor` + // From https://searchfox.org/mozilla-esr68/rev/080f9ed47742644d2ff84f7aa0b10aea5c4… + browser.onionlocationpopupnotificationanchor = win.document.getElementById( + ONIONLOCATION_BUTTON_ID + ); + + browser._onionLocationPrompt = win.PopupNotifications.show( + browser, + NOTIFICATION_ID, + NOTIFICATION_DESCRIPTION, + NOTIFICATION_ANCHOR_ID, + mainAction, + [cancelAction], + options + ); + } + + static setEnabled(browser) { + const win = browser.ownerGlobal; + const label = win.document.getElementById(ONIONLOCATION_LABEL_ID); + label.textContent = STRING_ONION_AVAILABLE; + const elem = win.document.getElementById(ONIONLOCATION_BOX_ID); + elem.removeAttribute("hidden"); + } + + static setDisabled(browser) { + const win = browser.ownerGlobal; + const elem = win.document.getElementById(ONIONLOCATION_BOX_ID); + elem.setAttribute("hidden", true); + } + + static updateOnionLocationBadge(browser) { + if (browser._onionLocation) { + OnionLocationParent.setEnabled(browser); + OnionLocationParent.showNotification(browser); + } else { + OnionLocationParent.setDisabled(browser); + } + } +} diff --git a/browser/components/onionservices/content/onionlocation-notification-icons.css b/browser/components/onionservices/content/onionlocation-notification-icons.css new file mode 100644 index 000000000000..7c8a6d892c6f --- /dev/null +++ b/browser/components/onionservices/content/onionlocation-notification-icons.css @@ -0,0 +1,5 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ + +.onionlocation-notification-icon { + display: none; +} \ No newline at end of file diff --git a/browser/components/onionservices/content/onionlocation-urlbar.css b/browser/components/onionservices/content/onionlocation-urlbar.css new file mode 100644 index 000000000000..91cad5f178d1 --- /dev/null +++ b/browser/components/onionservices/content/onionlocation-urlbar.css @@ -0,0 +1,27 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ + +#onion-location-button { + list-style-image: url(chrome://browser/content/onionservices/onionlocation.svg); +} + +#onion-location-box { + border-radius: 3px; + background-color: #6200A4; + padding-left: 5px; + padding-right: 5px; + color: white; + -moz-context-properties: fill; + fill: white; +} + +#onion-location-box:hover { + background-color: #0060DF !important; +} + +toolbar[brighttext] #onion-location-box { + background-color: #9400ff; +} + +toolbar[brighttext] #onion-location-box:hover { + background-color: #0060DF !important; +} diff --git a/browser/components/onionservices/content/onionlocation-urlbar.inc.xhtml b/browser/components/onionservices/content/onionlocation-urlbar.inc.xhtml new file mode 100644 index 000000000000..b612a4236f3c --- /dev/null +++ b/browser/components/onionservices/content/onionlocation-urlbar.inc.xhtml @@ -0,0 +1,10 @@ +# Copyright (c) 2020, The Tor Project, Inc. + +<hbox id="onion-location-box" + class="urlbar-icon-wrapper urlbar-page-action" + role="button" + hidden="true" + onclick="OnionLocationParent.buttonClick(event);"> + <image id="onion-location-button" role="presentation"/> + <hbox id="onion-label-container"><label id="onion-label"/></hbox> +</hbox> diff --git a/browser/components/onionservices/content/onionlocation.svg b/browser/components/onionservices/content/onionlocation.svg new file mode 100644 index 000000000000..37f40ac1812f --- /dev/null +++ b/browser/components/onionservices/content/onionlocation.svg @@ -0,0 +1,3 @@ +<svg width="16" height="16" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <path fill="context-fill" fill-opacity="context-fill-opacity" d="m8.016411 14.54499v-0.969784c3.071908-0.0089 5.559239-2.501304 5.559239-5.575429 0-3.073903-2.487331-5.566336-5.559239-5.575206v-0.9697843c3.607473 0.00909 6.528802 2.935521 6.528802 6.544991 0 3.609691-2.921329 6.536342-6.528802 6.545213zm0-3.394356c1.732661-0.0091 3.135111-1.415756 3.135111-3.150857 0-1.734878-1.402451-3.141542-3.135111-3.150634v-0.9695626c2.268448 0.00887 4.104895 1.849753 4.104895 4.120197 0 2.270666-1.836447 4.111549-4.104895 4.120419zm0-4.846926c0.9294227 0.00887 1.680545 0.7644289 1.680545 1.696069 0 0.9318627-0.7511226 1.687421-1.680545 1.696291zm-8.016411 1.696069c0 4.418473 3.581527 8.000222 8 8.000222 4.418251 0 8-3.581749 8-8.000222 0-4.418251-3.581749-7.999778-8-7.999778-4.418473 0-8 3.581527-8 7.999778z" /> +</svg> \ No newline at end of file diff --git a/browser/components/onionservices/content/onionlocationPreferences.inc.xhtml b/browser/components/onionservices/content/onionlocationPreferences.inc.xhtml new file mode 100644 index 000000000000..c285f403f99b --- /dev/null +++ b/browser/components/onionservices/content/onionlocationPreferences.inc.xhtml @@ -0,0 +1,11 @@ +# Copyright (c) 2020, The Tor Project, Inc. + +<groupbox id="onionServicesGroup" data-category="panePrivacy" data-subcategory="onionservices" hidden="true"> + <label><html:h2 id="onionServicesTitle"></html:h2></label> + <label><label class="tail-with-learn-more" id="prioritizeOnionsDesc"></label><label + class="learnMore" is="text-link" id="onionServicesLearnMore"></label></label> + <radiogroup id="prioritizeOnionsRadioGroup" aria-labelledby="prioritizeOnionsDesc" preference="privacy.prioritizeonions.enabled"> + <radio id="onionServicesRadioAlways" value="true"/> + <radio id="onionServicesRadioAsk" value="false"/> + </radiogroup> +</groupbox> diff --git a/browser/components/onionservices/content/onionlocationPreferences.js b/browser/components/onionservices/content/onionlocationPreferences.js new file mode 100644 index 000000000000..aa569b54721c --- /dev/null +++ b/browser/components/onionservices/content/onionlocationPreferences.js @@ -0,0 +1,31 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +ChromeUtils.defineModuleGetter( + this, + "TorStrings", + "resource:///modules/TorStrings.jsm" +); + +const OnionLocationPreferences = { + init() { + document.getElementById("onionServicesTitle").textContent = + TorStrings.onionLocation.onionServicesTitle; + document.getElementById("prioritizeOnionsDesc").textContent = + TorStrings.onionLocation.prioritizeOnionsDescription; + const learnMore = document.getElementById("onionServicesLearnMore"); + learnMore.textContent = TorStrings.onionLocation.learnMore; + learnMore.href = TorStrings.onionLocation.learnMoreURL; + document.getElementById("onionServicesRadioAlways").label = + TorStrings.onionLocation.always; + document.getElementById("onionServicesRadioAsk").label = + TorStrings.onionLocation.askEverytime; + }, +}; + +Object.defineProperty(this, "OnionLocationPreferences", { + value: OnionLocationPreferences, + enumerable: true, + writable: false, +}); diff --git a/browser/components/onionservices/jar.mn b/browser/components/onionservices/jar.mn index 9d6ce88d1841..f45b16dc5d29 100644 --- a/browser/components/onionservices/jar.mn +++ b/browser/components/onionservices/jar.mn @@ -7,3 +7,5 @@ browser.jar: content/browser/onionservices/onionservices.css (content/onionservices.css) content/browser/onionservices/savedKeysDialog.js (content/savedKeysDialog.js) content/browser/onionservices/savedKeysDialog.xhtml (content/savedKeysDialog.xhtml) + content/browser/onionservices/onionlocationPreferences.js (content/onionlocationPreferences.js) + content/browser/onionservices/onionlocation.svg (content/onionlocation.svg) diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build index e4b6d73f8f40..dfd664df434e 100644 --- a/browser/components/onionservices/moz.build +++ b/browser/components/onionservices/moz.build @@ -4,4 +4,6 @@ EXTRA_JS_MODULES += [ 'ExtensionMessaging.jsm', 'HttpsEverywhereControl.jsm', 'OnionAliasStore.jsm', + 'OnionLocationChild.jsm', + 'OnionLocationParent.jsm', ] diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml index d1f6a1010c1c..49e4f7c93c2f 100644 --- a/browser/components/preferences/privacy.inc.xhtml +++ b/browser/components/preferences/privacy.inc.xhtml @@ -14,6 +14,8 @@ <html:h1 data-l10n-id="privacy-header"/> </hbox> +#include ../onionservices/content/onionlocationPreferences.inc.xhtml + <!-- Tracking / Content Blocking --> <groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" aria-describedby="contentBlockingDescription"> <label id="contentBlockingHeader"><html:h2 data-l10n-id="content-blocking-enhanced-tracking-protection"/></label> diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js index 0fa239a6bab2..2ce47edc678a 100644 --- a/browser/components/preferences/privacy.js +++ b/browser/components/preferences/privacy.js @@ -90,6 +90,12 @@ XPCOMUtils.defineLazyScriptGetter( "chrome://browser/content/securitylevel/securityLevel.js" ); +XPCOMUtils.defineLazyScriptGetter( + this, + ["OnionLocationPreferences"], + "chrome://browser/content/onionservices/onionlocationPreferences.js" +); + XPCOMUtils.defineLazyServiceGetter( this, "listManager", @@ -158,6 +164,9 @@ Preferences.addAll([ // Do not track { id: "privacy.donottrackheader.enabled", type: "bool" }, + // Onion Location + { id: "privacy.prioritizeonions.enabled", type: "bool" }, + // Media { id: "media.autoplay.default", type: "int" }, @@ -304,6 +313,13 @@ var gPrivacyPane = { window.addEventListener("unload", unload); }, + /** + * Show the OnionLocation preferences UI + */ + _initOnionLocation() { + OnionLocationPreferences.init(); + }, + /** * Whether the prompt to restart Firefox should appear when changing the autostart pref. */ @@ -510,6 +526,7 @@ var gPrivacyPane = { this._initTrackingProtectionExtensionControl(); OnionServicesAuthPreferences.init(); this._initSecurityLevel(); + this._initOnionLocation(); Services.telemetry.setEventRecordingEnabled("pwmgr", true); diff --git a/browser/themes/shared/notification-icons.inc.css b/browser/themes/shared/notification-icons.inc.css index e4045a3912be..ce52111ce6b9 100644 --- a/browser/themes/shared/notification-icons.inc.css +++ b/browser/themes/shared/notification-icons.inc.css @@ -422,3 +422,5 @@ html|*#webRTC-previewVideo { background: #FFE900 url(chrome://browser/skin/notification-icons/update.svg) no-repeat center; border-radius: 50%; } + +%include ../../components/onionservices/content/onionlocation-notification-icons.css \ 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 a5da19eb186f..4950f3d12708 100644 --- a/browser/themes/shared/urlbar-searchbar.inc.css +++ b/browser/themes/shared/urlbar-searchbar.inc.css @@ -871,3 +871,5 @@ .searchbar-search-button:hover:not([addengines=true]) > .searchbar-search-icon-overlay:-moz-locale-dir(rtl) { margin-inline: -26px 20px; } + +%include ../../components/onionservices/content/onionlocation-urlbar.css diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index a0a53ddca585..32d8c0a114d2 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -2580,6 +2580,7 @@ void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup, // mDocumentURI. mDocumentBaseURI = nullptr; mChromeXHRDocBaseURI = nullptr; + mOnionLocationURI = nullptr; // Check if the current document is the top-level DevTools document. // For inner DevTools frames, mIsDevToolsDocument will be set when @@ -6068,6 +6069,22 @@ void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const { } } +static bool IsValidOnionLocation(nsIURI* aDocumentURI, + nsIURI* aOnionLocationURI) { + bool isHttpish; + nsAutoCString host; + return aDocumentURI && aOnionLocationURI && + NS_SUCCEEDED(aDocumentURI->SchemeIs("https", &isHttpish)) && + isHttpish && NS_SUCCEEDED(aDocumentURI->GetAsciiHost(host)) && + !StringEndsWith(host, ".onion"_ns) && + ((NS_SUCCEEDED(aOnionLocationURI->SchemeIs("http", &isHttpish)) && + isHttpish) || + (NS_SUCCEEDED(aOnionLocationURI->SchemeIs("https", &isHttpish)) && + isHttpish)) && + NS_SUCCEEDED(aOnionLocationURI->GetAsciiHost(host)) && + StringEndsWith(host, ".onion"_ns); +} + void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) { if (!aHeaderField) { NS_ERROR("null headerField"); @@ -6143,6 +6160,21 @@ void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) { aHeaderField == nsGkAtoms::handheldFriendly) { mViewportType = Unknown; } + + if (aHeaderField == nsGkAtoms::headerOnionLocation && !aData.IsEmpty()) { + nsCOMPtr<nsIURI> onionURI; + if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(onionURI), aData)) && + IsValidOnionLocation(Document::GetDocumentURI(), onionURI)) { + if (StaticPrefs::privacy_prioritizeonions_enabled()) { + nsCOMPtr<nsIRefreshURI> refresher(mDocumentContainer); + if (refresher) { + refresher->RefreshURI(onionURI, NodePrincipal(), 0, false, true); + } + } else { + mOnionLocationURI = onionURI; + } + } + } } void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource, @@ -10193,7 +10225,7 @@ void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) { static const char* const headers[] = { "default-style", "content-style-type", "content-language", "content-disposition", "refresh", "x-dns-prefetch-control", - "x-frame-options", + "x-frame-options", "onion-location", // add more http headers if you need // XXXbz don't add content-location support without reading bug // 238654 and its dependencies/dups first. diff --git a/dom/base/Document.h b/dom/base/Document.h index 21818ea01097..233e64058cc2 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -3322,6 +3322,7 @@ class Document : public nsINode, void ReleaseCapture() const; void MozSetImageElement(const nsAString& aImageElementId, Element* aElement); nsIURI* GetDocumentURIObject() const; + nsIURI* GetOnionLocationURI() const { return mOnionLocationURI; } // Not const because all the fullscreen goop is not const const char* GetFullscreenError(CallerType); bool FullscreenEnabled(CallerType aCallerType) { @@ -4201,6 +4202,7 @@ class Document : public nsINode, nsCOMPtr<nsIURI> mChromeXHRDocURI; nsCOMPtr<nsIURI> mDocumentBaseURI; nsCOMPtr<nsIURI> mChromeXHRDocBaseURI; + nsCOMPtr<nsIURI> mOnionLocationURI; // The base domain of the document for third-party checks. nsCString mBaseDomain; diff --git a/dom/webidl/Document.webidl b/dom/webidl/Document.webidl index 2a3dfc35eb7c..0200f44f00b2 100644 --- a/dom/webidl/Document.webidl +++ b/dom/webidl/Document.webidl @@ -686,3 +686,12 @@ partial interface Document { [ChromeOnly, Pure] readonly attribute nsIPermissionDelegateHandler permDelegateHandler; }; + + +/** + * Extension to allows chrome JS to know whether the document has a valid + * Onion-Location that we could redirect to. + */ +partial interface Document { + [ChromeOnly] readonly attribute URI? onionLocationURI; +}; diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 376448f60e00..7fcaa13f914d 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -8776,6 +8776,11 @@ value: false mirror: always +- name: privacy.prioritizeonions.enabled + type: RelaxedAtomicBool + value: false + mirror: always + #--------------------------------------------------------------------------- # Prefs starting with "prompts." #--------------------------------------------------------------------------- diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py index bd6b2115343f..13dbf3229d85 100644 --- a/xpcom/ds/StaticAtoms.py +++ b/xpcom/ds/StaticAtoms.py @@ -813,6 +813,7 @@ STATIC_ATOMS = [ Atom("oninputsourceschange","oninputsourceschange"), Atom("oninstall", "oninstall"), Atom("oninvalid", "oninvalid"), + Atom("headerOnionLocation", "onion-location"), Atom("onkeydown", "onkeydown"), Atom("onkeypress", "onkeypress"), Atom("onkeyup", "onkeyup"),
1 0
0 0
[tor-browser/tor-browser-80.0b2-10.0-1] Bug 40025: Remove Mozilla add-on install permissions
by gk@torproject.org 17 Aug '20

17 Aug '20
commit 879fe9945b20b47298f44921d074692d762c6d29 Author: Alex Catarineu <acat(a)torproject.org> Date: Mon Jul 27 18:12:55 2020 +0200 Bug 40025: Remove Mozilla add-on install permissions --- browser/app/permissions | 5 ----- 1 file changed, 5 deletions(-) diff --git a/browser/app/permissions b/browser/app/permissions index 4938bd1e22e5..5c4c302f5ba5 100644 --- a/browser/app/permissions +++ b/browser/app/permissions @@ -11,11 +11,6 @@ origin uitour 1 https://3g2upl4pq6kufc4m.onion origin uitour 1 about:tor -# XPInstall -origin install 1 https://addons.mozilla.org - # Remote troubleshooting origin remote-troubleshooting 1 https://support.mozilla.org -# addon install -origin install 1 https://fpn.firefox.com
1 0
0 0
[tor-browser/tor-browser-80.0b2-10.0-1] Bug 25741 - TBA: Disable GeckoNetworkManager
by gk@torproject.org 17 Aug '20

17 Aug '20
commit 765dcc0e0221c8abe009237d36f7bf255f19c17e 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 4fdb4d382739..1f5b6c09d04f 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
0 0
[tor-browser/tor-browser-80.0b2-10.0-1] Bug 33852: Clean up about:logins (LockWise) to avoid mentioning sync, etc.
by gk@torproject.org 17 Aug '20

17 Aug '20
commit f4aa9bb2a949861d02411e8fffe5d09970b0f3a6 Author: Kathy Brade <brade(a)pearlcrescent.com> Date: Tue Jul 14 11:15:07 2020 -0400 Bug 33852: Clean up about:logins (LockWise) to avoid mentioning sync, etc. Hide elements on about:logins that mention sync, "Firefox LockWise", and Mozilla's LockWise mobile apps. Disable the "Create New Login" button when security.nocertdb is true. --- browser/components/aboutlogins/AboutLoginsParent.jsm | 2 ++ browser/components/aboutlogins/content/aboutLogins.css | 8 +++++++- browser/components/aboutlogins/content/aboutLogins.js | 6 ++++++ .../aboutlogins/content/components/fxaccounts-button.css | 5 +++++ .../components/aboutlogins/content/components/menu-button.css | 10 ++++++++++ 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/browser/components/aboutlogins/AboutLoginsParent.jsm b/browser/components/aboutlogins/AboutLoginsParent.jsm index d52981936597..bb0b9aeff1bd 100644 --- a/browser/components/aboutlogins/AboutLoginsParent.jsm +++ b/browser/components/aboutlogins/AboutLoginsParent.jsm @@ -63,6 +63,7 @@ const PASSWORD_SYNC_NOTIFICATION_ID = "enable-password-sync"; const HIDE_MOBILE_FOOTER_PREF = "signon.management.page.hideMobileFooter"; const SHOW_PASSWORD_SYNC_NOTIFICATION_PREF = "signon.management.page.showPasswordSyncNotification"; +const NOCERTDB_PREF = "security.nocertdb"; // about:logins will always use the privileged content process, // even if it is disabled for other consumers such as about:newtab. @@ -432,6 +433,7 @@ class AboutLoginsParent extends JSWindowActorParent { importVisible: Services.policies.isAllowed("profileImport") && AppConstants.platform != "linux", + canCreateLogins: !Services.prefs.getBoolPref(NOCERTDB_PREF, false), }); await AboutLogins._sendAllLoginRelatedObjects( diff --git a/browser/components/aboutlogins/content/aboutLogins.css b/browser/components/aboutlogins/content/aboutLogins.css index 7ed29bda8297..dca63da2e649 100644 --- a/browser/components/aboutlogins/content/aboutLogins.css +++ b/browser/components/aboutlogins/content/aboutLogins.css @@ -69,6 +69,11 @@ login-item { grid-area: login; } +/* Do not promote Mozilla Sync in Tor Browser. */ +login-intro { + display: none !important; +} + #branding-logo { flex-basis: var(--sidebar-width); flex-shrink: 0; @@ -83,7 +88,8 @@ login-item { } } -:root:not(.official-branding) #branding-logo { +/* Hide "Firefox LockWise" branding in Tor Browser. */ +#branding-logo { visibility: hidden; } diff --git a/browser/components/aboutlogins/content/aboutLogins.js b/browser/components/aboutlogins/content/aboutLogins.js index a08427b0ae09..1669fba678bd 100644 --- a/browser/components/aboutlogins/content/aboutLogins.js +++ b/browser/components/aboutlogins/content/aboutLogins.js @@ -19,6 +19,9 @@ const gElements = { get loginFooter() { return this.loginItem.shadowRoot.querySelector("login-footer"); }, + get createNewLoginButton() { + return this.loginList.shadowRoot.querySelector(".create-login-button"); + }, }; let numberOfLogins = 0; @@ -100,6 +103,9 @@ window.addEventListener("AboutLoginsChromeToContent", event => { gElements.loginList.setSortDirection(event.detail.value.selectedSort); document.documentElement.classList.add("initialized"); gElements.loginList.classList.add("initialized"); + if (!event.detail.value.canCreateLogins) { + gElements.createNewLoginButton.disabled = true; + } break; } case "ShowLoginItemError": { diff --git a/browser/components/aboutlogins/content/components/fxaccounts-button.css b/browser/components/aboutlogins/content/components/fxaccounts-button.css index aefda548c84d..a02707980158 100644 --- a/browser/components/aboutlogins/content/components/fxaccounts-button.css +++ b/browser/components/aboutlogins/content/components/fxaccounts-button.css @@ -8,6 +8,11 @@ align-items: center; } +/* Do not promote Mozilla Sync in Tor Browser. */ +.logged-out-view { + display: none !important; +} + .fxaccounts-extra-text { /* Only show at most 3 lines of text to limit the text from overflowing the header. */ diff --git a/browser/components/aboutlogins/content/components/menu-button.css b/browser/components/aboutlogins/content/components/menu-button.css index f500edd5ae70..9cb663dc424c 100644 --- a/browser/components/aboutlogins/content/components/menu-button.css +++ b/browser/components/aboutlogins/content/components/menu-button.css @@ -89,3 +89,13 @@ .menuitem-mobile-android { background-image: url("chrome://browser/skin/logo-android.svg"); } + +/* + * Do not promote LockWise mobile apps in Tor Browser: hide the menu items + * and the separator line that precedes them. + */ +.menuitem-mobile-android, +.menuitem-mobile-ios, +button[data-event-name="AboutLoginsGetHelp"] + hr { + display: none !important; +}
1 0
0 0
[tor-browser/tor-browser-80.0b2-10.0-1] Bug 33342: Avoid disconnect search addon error after removal.
by gk@torproject.org 17 Aug '20

17 Aug '20
commit 14967794fcbe52de03739e5ed79be19827f03f98 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 78c0b0f7c702..ad8922dadebf 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
0 0
[tor-browser/tor-browser-80.0b2-10.0-1] Bug 28005: Implement .onion alias urlbar rewrites
by gk@torproject.org 17 Aug '20

17 Aug '20
commit d08db4785dd592530a975b713c28b6a32c2beeaa 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 --- browser/actors/ClickHandlerChild.jsm | 20 ++ browser/actors/ClickHandlerParent.jsm | 1 + browser/actors/ContextMenuChild.jsm | 4 + browser/base/content/browser-places.js | 12 +- browser/base/content/browser-siteIdentity.js | 12 +- browser/base/content/browser.js | 43 ++++- browser/base/content/nsContextMenu.js | 18 ++ browser/base/content/pageinfo/pageInfo.js | 2 +- browser/base/content/pageinfo/pageInfo.xhtml | 10 + browser/base/content/pageinfo/security.js | 17 +- browser/base/content/tabbrowser.js | 7 + browser/base/content/utilityOverlay.js | 12 ++ browser/components/BrowserGlue.jsm | 8 + .../onionservices/ExtensionMessaging.jsm | 77 ++++++++ .../onionservices/HttpsEverywhereControl.jsm | 119 ++++++++++++ .../components/onionservices/OnionAliasStore.jsm | 201 +++++++++++++++++++++ browser/components/onionservices/moz.build | 6 + browser/components/urlbar/UrlbarInput.jsm | 13 +- docshell/base/nsDocShell.cpp | 52 ++++++ docshell/base/nsDocShell.h | 6 + docshell/base/nsDocShellLoadState.cpp | 4 + docshell/base/nsIDocShell.idl | 5 + docshell/base/nsIWebNavigation.idl | 5 + docshell/shistory/SessionHistoryEntry.cpp | 14 ++ docshell/shistory/SessionHistoryEntry.h | 1 + docshell/shistory/nsISHEntry.idl | 5 + docshell/shistory/nsSHEntry.cpp | 22 ++- docshell/shistory/nsSHEntry.h | 1 + dom/interfaces/base/nsIBrowser.idl | 3 +- dom/ipc/BrowserChild.cpp | 2 + dom/ipc/BrowserParent.cpp | 3 +- dom/ipc/PBrowser.ipdl | 1 + modules/libpref/init/StaticPrefList.yaml | 6 + netwerk/dns/effective_tld_names.dat | 2 + netwerk/ipc/DocumentLoadListener.cpp | 10 + toolkit/content/widgets/browser-custom-element.js | 13 +- toolkit/modules/sessionstore/SessionHistory.jsm | 5 + 37 files changed, 720 insertions(+), 22 deletions(-) diff --git a/browser/actors/ClickHandlerChild.jsm b/browser/actors/ClickHandlerChild.jsm index d5f7f31f3280..1d147bb274f2 100644 --- a/browser/actors/ClickHandlerChild.jsm +++ b/browser/actors/ClickHandlerChild.jsm @@ -136,6 +136,26 @@ class ClickHandlerChild extends JSWindowActorChild { json.originStoragePrincipal = ownerDoc.effectiveStoragePrincipal; json.triggeringPrincipal = ownerDoc.nodePrincipal; + // Check if the link needs to be opened with .tor.onion urlbar rewrites + // allowed. Only when the owner doc has onionUrlbarRewritesAllowed = true + // and the same origin we should allow this. + json.onionUrlbarRewritesAllowed = false; + if (this.docShell.onionUrlbarRewritesAllowed) { + const sm = Services.scriptSecurityManager; + try { + let targetURI = Services.io.newURI(href); + let isPrivateWin = + ownerDoc.nodePrincipal.originAttributes.privateBrowsingId > 0; + sm.checkSameOriginURI( + docshell.currentDocumentChannel.URI, + targetURI, + false, + isPrivateWin + ); + json.onionUrlbarRewritesAllowed = true; + } catch (e) {} + } + // If a link element is clicked with middle button, user wants to open // the link somewhere rather than pasting clipboard content. Therefore, // when it's clicked with middle button, we should prevent multiple diff --git a/browser/actors/ClickHandlerParent.jsm b/browser/actors/ClickHandlerParent.jsm index 75509b95ce7f..06d56624e316 100644 --- a/browser/actors/ClickHandlerParent.jsm +++ b/browser/actors/ClickHandlerParent.jsm @@ -99,6 +99,7 @@ class ClickHandlerParent extends JSWindowActorParent { charset: browser.characterSet, referrerInfo: E10SUtils.deserializeReferrerInfo(data.referrerInfo), allowMixedContent: data.allowMixedContent, + onionUrlbarRewritesAllowed: data.onionUrlbarRewritesAllowed, isContentWindowPrivate: data.isContentWindowPrivate, originPrincipal: data.originPrincipal, originStoragePrincipal: data.originStoragePrincipal, diff --git a/browser/actors/ContextMenuChild.jsm b/browser/actors/ContextMenuChild.jsm index ae761bd5116a..3779c41620e8 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 6e6ff3958b7c..2e1b4e93d832 100644 --- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -457,7 +457,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; @@ -561,7 +562,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)) { @@ -1577,14 +1578,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 54685f828b23..7c210f0a1e10 100644 --- a/browser/base/content/browser-siteIdentity.js +++ b/browser/base/content/browser-siteIdentity.js @@ -533,13 +533,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; @@ -625,17 +625,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; } }, @@ -1076,11 +1077,12 @@ var gIdentityHandler = { this.updateSitePermissions(); }, - setURI(uri) { + setURI(uri, onionAliasURI) { if (uri.schemeIs("view-source")) { uri = Services.io.newURI(uri.spec.replace(/^view-source:/i, "")); } this._uri = uri; + this._onionAliasURI = onionAliasURI; try { // Account for file: urls and catch when "" is the value diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 7b1ad4773ab6..9a7005889aa3 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", @@ -2220,6 +2221,7 @@ var gBrowserInit = { // [9]: allowInheritPrincipal (bool) // [10]: csp (nsIContentSecurityPolicy) // [11]: nsOpenWindowInfo + // [12]: onionUrlbarRewritesAllowed (bool) let userContextId = window.arguments[5] != undefined ? window.arguments[5] @@ -2239,7 +2241,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 { @@ -3134,7 +3137,8 @@ function loadURI( forceAboutBlankViewerInCurrent, triggeringPrincipal, allowInheritPrincipal = false, - csp = null + csp = null, + onionUrlbarRewritesAllowed = false ) { if (!triggeringPrincipal) { throw new Error("Must load with a triggering Principal"); @@ -3152,6 +3156,7 @@ function loadURI( csp, forceAboutBlankViewerInCurrent, allowInheritPrincipal, + onionUrlbarRewritesAllowed, }); } catch (e) { Cu.reportError(e); @@ -5273,11 +5278,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(); @@ -5449,6 +5467,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 @@ -5466,7 +5485,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 @@ -6914,6 +6933,21 @@ function handleLinkClick(event, href, linkNode) { } catch (e) {} } + // Check if the link needs to be opened with .tor.onion urlbar rewrites + // allowed. Only when the owner doc has onionUrlbarRewritesAllowed = true + // and the same origin we should allow this. + let persistOnionUrlbarRewritesAllowedInChildTab = false; + if (where == "tab" && gBrowser.docShell.onionUrlbarRewritesAllowed) { + const sm = Services.scriptSecurityManager; + try { + let tURI = makeURI(href); + let isPrivateWin = + doc.nodePrincipal.originAttributes.privateBrowsingId > 0; + sm.checkSameOriginURI(doc.documentURIObject, tURI, false, isPrivateWin); + persistOnionUrlbarRewritesAllowedInChildTab = true; + } catch (e) {} + } + let frameID = WebNavigationFrames.getFrameId(doc.defaultView); urlSecurityCheck(href, doc.nodePrincipal); @@ -6926,6 +6960,7 @@ function handleLinkClick(event, href, linkNode) { triggeringPrincipal: doc.nodePrincipal, csp: doc.csp, frameID, + onionUrlbarRewritesAllowed: persistOnionUrlbarRewritesAllowedInChildTab, }; // The new tab/window must use the same userContextId diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js index 329b04b08b5e..597e30172eba 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -58,6 +58,7 @@ function openContextMenu(aMessage, aBrowser, aActor) { disableSetDesktopBackground: data.disableSetDesktopBackground, loginFillInfo: data.loginFillInfo, parentAllowsMixedContent: data.parentAllowsMixedContent, + parentAllowsOnionUrlbarRewrites: data.parentAllowsOnionUrlbarRewrites, userContextId: data.userContextId, webExtContextData: data.webExtContextData, }; @@ -1045,6 +1046,7 @@ class nsContextMenu { triggeringPrincipal: this.principal, csp: this.csp, frameID: this.contentData.frameID, + onionUrlbarRewritesAllowed: false, }; for (let p in extra) { params[p] = extra[p]; @@ -1068,6 +1070,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 89d6b98899a6..627eb449b32f 100644 --- a/browser/base/content/pageinfo/pageInfo.js +++ b/browser/base/content/pageinfo/pageInfo.js @@ -398,7 +398,7 @@ async function onNonMediaPageInfoLoad(browser, pageInfoData, imageInfo) { ); } onLoadPermission(uri, principal); - securityOnLoad(uri, windowInfo); + securityOnLoad(uri, windowInfo, browser.currentOnionAliasURI); } function resetPageInfo(args) { diff --git a/browser/base/content/pageinfo/pageInfo.xhtml b/browser/base/content/pageinfo/pageInfo.xhtml index f40ffd3778d8..a23f2bb5748c 100644 --- a/browser/base/content/pageinfo/pageInfo.xhtml +++ b/browser/base/content/pageinfo/pageInfo.xhtml @@ -312,6 +312,16 @@ <input id="security-identity-domain-value" readonly="readonly"/> </td> </tr> + <!-- Onion Alias --> + <tr id="security-view-identity-onionalias-row"> + <th> + <xul:label id="security-view-identity-onionalias" + control="security-view-identity-onionalias-value"/> + </th> + <td> + <input id="security-view-identity-onionalias-value" readonly="true"/> + </td> + </tr> <!-- Owner --> <tr> <th> diff --git a/browser/base/content/pageinfo/security.js b/browser/base/content/pageinfo/security.js index b25b125a03ff..b9946f33622f 100644 --- a/browser/base/content/pageinfo/security.js +++ b/browser/base/content/pageinfo/security.js @@ -240,7 +240,7 @@ var security = { }, }; -async function securityOnLoad(uri, windowInfo) { +async function securityOnLoad(uri, windowInfo, onionAliasURI) { await security.init(uri, windowInfo); let info = security.securityInfo; @@ -253,6 +253,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 3328e7e4d700..60a4711e946d 100644 --- a/browser/base/content/tabbrowser.js +++ b/browser/base/content/tabbrowser.js @@ -1544,6 +1544,7 @@ var aRelatedToCurrent; var aAllowInheritPrincipal; var aAllowMixedContent; + var aOnionUrlbarRewritesAllowed; var aSkipAnimation; var aForceNotRemote; var aPreferredRemoteType; @@ -1574,6 +1575,7 @@ aRelatedToCurrent = params.relatedToCurrent; aAllowInheritPrincipal = !!params.allowInheritPrincipal; aAllowMixedContent = params.allowMixedContent; + aOnionUrlbarRewritesAllowed = params.onionUrlbarRewritesAllowed; aSkipAnimation = params.skipAnimation; aForceNotRemote = params.forceNotRemote; aPreferredRemoteType = params.preferredRemoteType; @@ -1615,6 +1617,7 @@ relatedToCurrent: aRelatedToCurrent, skipAnimation: aSkipAnimation, allowMixedContent: aAllowMixedContent, + onionUrlbarRewritesAllowed: aOnionUrlbarRewritesAllowed, forceNotRemote: aForceNotRemote, createLazyBrowser: aCreateLazyBrowser, preferredRemoteType: aPreferredRemoteType, @@ -2415,6 +2418,7 @@ { allowInheritPrincipal, allowMixedContent, + onionUrlbarRewritesAllowed, allowThirdPartyFixup, bulkOrderedOpen, charset, @@ -2748,6 +2752,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 caed4a0b4426..f54928cc8497 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 8dafe18966e4..c6ad31c3d3f6 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -742,6 +742,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", @@ -2030,6 +2031,7 @@ BrowserGlue.prototype = { Normandy.uninit(); RFPHelper.uninit(); + OnionAliasStore.uninit(); }, // Set up a listener to enable/disable the screenshots extension @@ -2473,6 +2475,12 @@ BrowserGlue.prototype = { }, }, + { + task: () => { + OnionAliasStore.init(); + }, + }, + { task: () => { Blocklist.loadBlocklistAsync(); diff --git a/browser/components/onionservices/ExtensionMessaging.jsm b/browser/components/onionservices/ExtensionMessaging.jsm new file mode 100644 index 000000000000..c93b8c6edf85 --- /dev/null +++ b/browser/components/onionservices/ExtensionMessaging.jsm @@ -0,0 +1,77 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +const EXPORTED_SYMBOLS = ["ExtensionMessaging"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { ExtensionUtils } = ChromeUtils.import( + "resource://gre/modules/ExtensionUtils.jsm" +); +const { MessageChannel } = ChromeUtils.import( + "resource://gre/modules/MessageChannel.jsm" +); +const { AddonManager } = ChromeUtils.import( + "resource://gre/modules/AddonManager.jsm" +); + +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); + +XPCOMUtils.defineLazyModuleGetters(this, { + ExtensionParent: "resource://gre/modules/ExtensionParent.jsm", +}); + +class ExtensionMessaging { + constructor() { + this._callback = null; + this._handlers = new Map(); + this._messageManager = Services.cpmm; + } + + async sendMessage(message, extensionId) { + const addon = await AddonManager.getAddonByID(extensionId); + if (!addon) { + throw new Error(`extension '${extensionId} does not exist`); + } + await addon.startupPromise; + + const { torSendExtensionMessage } = ExtensionParent; + return torSendExtensionMessage(extensionId, message); + } + + unload() { + if (this._callback) { + this._handlers.clear(); + this._messageManager.removeMessageListener( + "MessageChannel:Response", + this._callback + ); + this._callback = null; + } + } + + _onMessage({ data }) { + const channelId = data.messageName; + if (this._handlers.has(channelId)) { + const { resolve, reject } = this._handlers.get(channelId); + this._handlers.delete(channelId); + if (data.error) { + reject(new Error(data.error.message)); + } else { + resolve(data.value); + } + } + } + + _init() { + if (this._callback === null) { + this._callback = this._onMessage.bind(this); + this._messageManager.addMessageListener( + "MessageChannel:Response", + this._callback + ); + } + } +} diff --git a/browser/components/onionservices/HttpsEverywhereControl.jsm b/browser/components/onionservices/HttpsEverywhereControl.jsm new file mode 100644 index 000000000000..60c3b5fca282 --- /dev/null +++ b/browser/components/onionservices/HttpsEverywhereControl.jsm @@ -0,0 +1,119 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +const EXPORTED_SYMBOLS = ["HttpsEverywhereControl"]; + +const { ExtensionMessaging } = ChromeUtils.import( + "resource:///modules/ExtensionMessaging.jsm" +); +const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm"); + +const EXTENSION_ID = "https-everywhere-eff(a)eff.org"; +const SECUREDROP_TOR_ONION_CHANNEL = { + name: "SecureDropTorOnion", + jwk: { + kty: "RSA", + e: "AQAB", + n: + "p10BbUVc5Xj2S_-MH3bACNBaISo_r9e3PVPyTTjsGsdg2qSXvqUO42fBtpFAy0zUzIGS83v4JjiRdvKJaZTIvbC8AcpymzdsTqujMm8RPTSy3hO_8mXzGa4DEsIB1uNLnUWRBKXvSGCmT9kFyxhTpkYqokNBzafVihTU34tN2Md1xFHnmZGqfYtPtbJLWAa5Z1M11EyR4lIyUxIiPTV9t1XstDbWr3iS83REJrGEFmjG1-BAgx8_lDUTa41799N2yYEhgZud7bL0M3ei8s5OERjiion5uANkUV3-s2QqUZjiVA-XR_HizXjciaUWNd683KqekpNOZ_0STh_UGwpcwU-KwG07QyiCrLrRpz8S_vH8CqGrrcWY3GSzYe9dp34jJdO65oA-G8tK6fMXtvTCFDZI6oNNaXJH71F5J0YbqO2ZqwKYc2WSi0gKVl2wd9roOVjaBmkJqvocntYuNM7t38fDEWHn5KUkmrTbiG68Cy56tDUfpKl3D9Uj4LaMvxJ1tKGvzQ4k_60odT7gIxu6DqYjXUHZpwPsSGBq3njaD7boe4CUXF2K7ViOc87BsKxRNCzDD8OklRjjXzOTOBH3PqFJ93CJ-4ECE5t9STU20aZ8E-2zKB8vjKyCySE4-kcIvBBsnkwVaJTPy9Ft1qYybo-soXEWVEZATANNWklBt8k", + }, + update_path_prefix: "https://securedrop.org/https-everywhere/", + scope: + "^https?:\\/\\/[a-z0-9-]+(?:\\.[a-z0-9-]+)*\\.securedrop\\.tor\\.onion\\/", + replaces_default_rulesets: false, +}; + +class HttpsEverywhereControl { + constructor() { + this._extensionMessaging = null; + } + + async _sendMessage(type, object) { + return this._extensionMessaging.sendMessage( + { + type, + object, + }, + EXTENSION_ID + ); + } + + static async wait(seconds = 1) { + return new Promise(resolve => setTimeout(resolve, seconds * 1000)); + } + + /** + * Installs the .tor.onion update channel in https-everywhere + */ + async installTorOnionUpdateChannel(retries = 5) { + this._init(); + + // TODO: https-everywhere store is initialized asynchronously, so sending a message + // immediately results in a `store.get is undefined` error. + // For now, let's wait a bit and retry a few times if there is an error, but perhaps + // we could suggest https-everywhere to send a message when that happens and listen + // for that here. + await HttpsEverywhereControl.wait(); + + try { + // TODO: we may want a way to "lock" this update channel, so that it cannot be modified + // by the user via UI, but I think this is not possible at the time of writing via + // the existing messages in https-everywhere. + await this._sendMessage( + "create_update_channel", + SECUREDROP_TOR_ONION_CHANNEL.name + ); + } catch (e) { + if (retries <= 0) { + throw new Error("Could not install SecureDropTorOnion update channel"); + } + await this.installTorOnionUpdateChannel(retries - 1); + return; + } + + await this._sendMessage( + "update_update_channel", + SECUREDROP_TOR_ONION_CHANNEL + ); + } + + /** + * Returns the .tor.onion rulesets available in https-everywhere + */ + async getTorOnionRules() { + return this._sendMessage("get_simple_rules_ending_with", ".tor.onion"); + } + + /** + * Returns the timestamp of the last .tor.onion update channel update. + */ + async getRulesetTimestamp() { + const rulesets = await this._sendMessage("get_ruleset_timestamps"); + const securedrop = + rulesets && + rulesets.find(([{ name }]) => name === SECUREDROP_TOR_ONION_CHANNEL.name); + if (securedrop) { + const [ + updateChannel, // This has the same structure as SECUREDROP_TOR_ONION_CHANNEL + lastUpdatedTimestamp, // An integer, 0 if the update channel was never updated + ] = securedrop; + void updateChannel; // Ignore eslint unused warning for ruleset + return lastUpdatedTimestamp; + } + return null; + } + + unload() { + if (this._extensionMessaging) { + this._extensionMessaging.unload(); + this._extensionMessaging = null; + } + } + + _init() { + if (!this._extensionMessaging) { + this._extensionMessaging = new ExtensionMessaging(); + } + } +} diff --git a/browser/components/onionservices/OnionAliasStore.jsm b/browser/components/onionservices/OnionAliasStore.jsm new file mode 100644 index 000000000000..66cf569227bf --- /dev/null +++ b/browser/components/onionservices/OnionAliasStore.jsm @@ -0,0 +1,201 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +const EXPORTED_SYMBOLS = ["OnionAliasStore"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); +const { setTimeout, clearTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" +); +const { HttpsEverywhereControl } = ChromeUtils.import( + "resource:///modules/HttpsEverywhereControl.jsm" +); + +// Logger adapted from CustomizableUI.jsm +const kPrefOnionAliasDebug = "browser.onionalias.debug"; +XPCOMUtils.defineLazyPreferenceGetter( + this, + "gDebuggingEnabled", + kPrefOnionAliasDebug, + false, + (pref, oldVal, newVal) => { + if (typeof log != "undefined") { + log.maxLogLevel = newVal ? "all" : "log"; + } + } +); +XPCOMUtils.defineLazyGetter(this, "log", () => { + let scope = {}; + ChromeUtils.import("resource://gre/modules/Console.jsm", scope); + let consoleOptions = { + maxLogLevel: gDebuggingEnabled ? "all" : "log", + prefix: "OnionAlias", + }; + return new scope.ConsoleAPI(consoleOptions); +}); + +function observe(topic, callback) { + let observer = { + observe(aSubject, aTopic, aData) { + if (topic === aTopic) { + callback(aSubject, aData); + } + }, + }; + Services.obs.addObserver(observer, topic); + return () => Services.obs.removeObserver(observer, topic); +} + +class _OnionAliasStore { + static get RULESET_CHECK_INTERVAL() { + return 1000 * 60; // 1 minute + } + + static get RULESET_CHECK_INTERVAL_FAST() { + return 1000 * 5; // 5 seconds + } + + constructor() { + this._onionMap = new Map(); + this._rulesetTimeout = null; + this._removeObserver = () => {}; + this._canLoadRules = false; + this._rulesetTimestamp = null; + this._updateChannelInstalled = false; + } + + async _periodicRulesetCheck() { + // TODO: it would probably be preferable to listen to some message broadcasted by + // the https-everywhere extension when some update channel is updated, instead of + // polling every N seconds. + log.debug("Checking for new rules"); + const ts = await this.httpsEverywhereControl.getRulesetTimestamp(); + log.debug( + `Found ruleset timestamp ${ts}, current is ${this._rulesetTimestamp}` + ); + if (ts !== this._rulesetTimestamp) { + this._rulesetTimestamp = ts; + log.debug("New rules found, updating"); + // We clear the mappings even if we cannot load the rules from https-everywhere, + // since we cannot be sure if the stored mappings are correct anymore. + this._clear(); + if (this._canLoadRules) { + await this._loadRules(); + } + } + // If the timestamp is 0, that means the update channel was not yet updated, so + // we schedule a check soon. + this._rulesetTimeout = setTimeout( + () => this._periodicRulesetCheck(), + ts === 0 + ? _OnionAliasStore.RULESET_CHECK_INTERVAL_FAST + : _OnionAliasStore.RULESET_CHECK_INTERVAL + ); + } + + async init() { + this.httpsEverywhereControl = new HttpsEverywhereControl(); + + // Setup .tor.onion rule loading. + // The http observer is a fallback, and is removed in _loadRules() as soon as we are able + // to load some rules from HTTPS Everywhere. + this._loadHttpObserver(); + try { + await this.httpsEverywhereControl.installTorOnionUpdateChannel(); + this._updateChannelInstalled = true; + await this.httpsEverywhereControl.getTorOnionRules(); + this._canLoadRules = true; + } catch (e) { + // Loading rules did not work, probably because "get_simple_rules_ending_with" is not yet + // working in https-everywhere. Use an http observer as a fallback for learning the rules. + log.debug(`Could not load rules: ${e.message}`); + } + + // Setup checker for https-everywhere ruleset updates + if (this._updateChannelInstalled) { + this._periodicRulesetCheck(); + } + } + + /** + * Loads the .tor.onion mappings from https-everywhere. + */ + async _loadRules() { + const rules = await this.httpsEverywhereControl.getTorOnionRules(); + // Remove http observer if we are able to load some rules directly. + if (rules.length) { + this._removeObserver(); + this._removeObserver = () => {}; + } + this._clear(); + log.debug(`Loading ${rules.length} rules`, rules); + for (const rule of rules) { + // Here we are trusting that the securedrop ruleset follows some conventions so that we can + // assume there is a host mapping from `rule.host` to the hostname of the URL in `rule.to`. + try { + const url = new URL(rule.to); + const shortHost = rule.host; + const longHost = url.hostname; + this._addMapping(shortHost, longHost); + } catch (e) { + log.error("Could not process rule:", rule); + } + } + } + + /** + * Loads a http observer to listen for local redirects for populating + * the .tor.onion -> .onion mappings. Should only be used if we cannot ask https-everywhere + * directly for the mappings. + */ + _loadHttpObserver() { + this._removeObserver = observe("http-on-before-connect", channel => { + if ( + channel.isMainDocumentChannel && + channel.originalURI.host.endsWith(".tor.onion") + ) { + this._addMapping(channel.originalURI.host, channel.URI.host); + } + }); + } + + uninit() { + this._clear(); + this._removeObserver(); + this._removeObserver = () => {}; + if (this.httpsEverywhereControl) { + this.httpsEverywhereControl.unload(); + delete this.httpsEverywhereControl; + } + clearTimeout(this._rulesetTimeout); + this._rulesetTimeout = null; + this._rulesetTimestamp = null; + } + + _clear() { + this._onionMap.clear(); + } + + _addMapping(shortOnionHost, longOnionHost) { + this._onionMap.set(longOnionHost, shortOnionHost); + } + + getShortURI(onionURI) { + if ( + (onionURI.schemeIs("http") || onionURI.schemeIs("https")) && + this._onionMap.has(onionURI.host) + ) { + return onionURI + .mutate() + .setHost(this._onionMap.get(onionURI.host)) + .finalize(); + } + return null; + } +} + +let OnionAliasStore = new _OnionAliasStore(); diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build index 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 8ef023b5615d..1214da194e41 100644 --- a/browser/components/urlbar/UrlbarInput.jsm +++ b/browser/components/urlbar/UrlbarInput.jsm @@ -293,7 +293,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); @@ -1622,7 +1625,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 994f7b5890f8..7e3fafa38b21 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -5603,6 +5603,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. @@ -8794,6 +8798,20 @@ nsresult nsDocShell::HandleSameDocumentNavigation( return NS_OK; } +/* static */ +bool nsDocShell::IsTorOnionRedirect(nsIURI* aOldURI, nsIURI* aNewURI) { + nsAutoCString oldHost; + nsAutoCString newHost; + if (aOldURI && aNewURI && NS_SUCCEEDED(aOldURI->GetHost(oldHost)) && + StringEndsWith(oldHost, ".tor.onion"_ns) && + NS_SUCCEEDED(aNewURI->GetHost(newHost)) && + StringEndsWith(newHost, ".onion"_ns) && + !StringEndsWith(newHost, ".tor.onion"_ns)) { + return true; + } + return false; +} + nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState) { MOZ_ASSERT(aLoadState, "need a load state!"); MOZ_ASSERT(aLoadState->TriggeringPrincipal(), @@ -8935,6 +8953,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 @@ -11164,6 +11206,7 @@ nsresult nsDocShell::AddToSessionHistory( HistoryID(), mDynamicallyCreated, originalURI, resultPrincipalURI, loadReplace, referrerInfo, srcdoc, srcdocEntry, baseURI, saveLayoutState, expired); + entry->SetOnionUrlbarRewritesAllowed(mOnionUrlbarRewritesAllowed); if (mBrowsingContext->IsTop() && GetSessionHistory()) { bool shouldPersist = ShouldAddToSessionHistory(aURI, aChannel); @@ -12951,3 +12994,12 @@ void nsDocShell::SetLoadingSessionHistoryInfo( const mozilla::dom::SessionHistoryInfo& aInfo) { mLoadingEntry = MakeUnique<SessionHistoryInfo>(aInfo); } + +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 391039a96294..eaaf4050c7ca 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 @@ -538,6 +541,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, @@ -1201,6 +1206,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 c0e758817e91..3b4592206b16 100644 --- a/docshell/base/nsDocShellLoadState.cpp +++ b/docshell/base/nsDocShellLoadState.cpp @@ -724,6 +724,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 22dae012b1a7..c64d8f12b1fd 100644 --- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -998,4 +998,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 eff31d352591..fc8185f1b44f 100644 --- a/docshell/base/nsIWebNavigation.idl +++ b/docshell/base/nsIWebNavigation.idl @@ -253,6 +253,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 44739973393d..e2fc05492afb 100644 --- a/docshell/shistory/SessionHistoryEntry.cpp +++ b/docshell/shistory/SessionHistoryEntry.cpp @@ -529,6 +529,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 b8a79aa771b1..1ee808736044 100644 --- a/docshell/shistory/SessionHistoryEntry.h +++ b/docshell/shistory/SessionHistoryEntry.h @@ -84,6 +84,7 @@ class SessionHistoryInfo { bool mIsSrcdocEntry = false; bool mScrollRestorationIsManual = false; bool mPersist = false; + bool mOnionUrlbarRewritesAllowed = false; }; // SessionHistoryEntry is used to store session history data in the parent diff --git a/docshell/shistory/nsISHEntry.idl b/docshell/shistory/nsISHEntry.idl index d17da38f6f38..1354ab9b6340 100644 --- a/docshell/shistory/nsISHEntry.idl +++ b/docshell/shistory/nsISHEntry.idl @@ -247,6 +247,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 99e12abc1fea..4cbf8a869d75 100644 --- a/docshell/shistory/nsSHEntry.cpp +++ b/docshell/shistory/nsSHEntry.cpp @@ -45,7 +45,8 @@ nsSHEntry::nsSHEntry() mScrollRestorationIsManual(false), mLoadedInThisProcess(false), mPersist(true), - mHasUserInteraction(false) {} + mHasUserInteraction(false), + mOnionUrlbarRewritesAllowed(false) {} nsSHEntry::nsSHEntry(const nsSHEntry& aOther) : mShared(aOther.mShared), @@ -72,7 +73,8 @@ nsSHEntry::nsSHEntry(const nsSHEntry& aOther) mScrollRestorationIsManual(false), mLoadedInThisProcess(aOther.mLoadedInThisProcess), mPersist(aOther.mPersist), - mHasUserInteraction(false) {} + mHasUserInteraction(false), + mOnionUrlbarRewritesAllowed(aOther.mOnionUrlbarRewritesAllowed) {} nsSHEntry::~nsSHEntry() { // Null out the mParent pointers on all our kids. @@ -853,6 +855,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(); @@ -902,6 +916,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 058f1720fa51..93e1d6696029 100644 --- a/docshell/shistory/nsSHEntry.h +++ b/docshell/shistory/nsSHEntry.h @@ -64,6 +64,7 @@ class nsSHEntry : public nsISHEntry { bool mLoadedInThisProcess; bool mPersist; bool mHasUserInteraction; + bool mOnionUrlbarRewritesAllowed; }; #endif /* nsSHEntry_h */ diff --git a/dom/interfaces/base/nsIBrowser.idl b/dom/interfaces/base/nsIBrowser.idl index d41abcc089ea..9c9ecdf52c0b 100644 --- a/dom/interfaces/base/nsIBrowser.idl +++ b/dom/interfaces/base/nsIBrowser.idl @@ -148,7 +148,8 @@ interface nsIBrowser : nsISupports in boolean aIsSynthetic, in boolean aHasRequestContextID, in uint64_t aRequestContextID, - in AString aContentType); + in AString aContentType, + in boolean aOnionUrlbarRewritesAllowed); /** * Determine what process switching behavior this browser element should have. diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp index 592ec8ade75a..6c39f1751bf6 100644 --- a/dom/ipc/BrowserChild.cpp +++ b/dom/ipc/BrowserChild.cpp @@ -3588,6 +3588,8 @@ NS_IMETHODIMP BrowserChild::OnLocationChange(nsIWebProgress* aWebProgress, docShell->GetMayEnableCharacterEncodingMenu(); locationChangeData->charsetAutodetected() = docShell->GetCharsetAutodetected(); + locationChangeData->onionUrlbarRewritesAllowed() = + docShell->GetOnionUrlbarRewritesAllowed(); locationChangeData->contentPrincipal() = document->NodePrincipal(); locationChangeData->contentPartitionedPrincipal() = diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp index 32fc5287d947..34d9532baa19 100644 --- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -2675,7 +2675,8 @@ mozilla::ipc::IPCResult BrowserParent::RecvOnLocationChange( aLocationChangeData->isSyntheticDocument(), aLocationChangeData->requestContextID().isSome(), aLocationChangeData->requestContextID().valueOr(0), - aLocationChangeData->contentType()); + aLocationChangeData->contentType(), + aLocationChangeData->onionUrlbarRewritesAllowed()); } GetBrowsingContext()->Top()->GetWebProgress()->OnLocationChange( diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index 5bd2ff244bcb..f6890065c725 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -135,6 +135,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 2a3c65e8144c..376448f60e00 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -1140,6 +1140,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 2e9ed397eb03..81ead7039fe4 100644 --- a/netwerk/dns/effective_tld_names.dat +++ b/netwerk/dns/effective_tld_names.dat @@ -5509,6 +5509,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 c0c4b537d92c..a03001ea3db8 100644 --- a/netwerk/ipc/DocumentLoadListener.cpp +++ b/netwerk/ipc/DocumentLoadListener.cpp @@ -2336,6 +2336,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 96079c2a0c3c..906bb0c005cd 100644 --- a/toolkit/content/widgets/browser-custom-element.js +++ b/toolkit/content/widgets/browser-custom-element.js @@ -236,6 +236,8 @@ this._mayEnableCharacterEncodingMenu = null; + this._onionUrlbarRewritesAllowed = false; + this._charsetAutodetected = false; this._contentPrincipal = null; @@ -637,6 +639,12 @@ } } + get onionUrlbarRewritesAllowed() { + return this.isRemoteBrowser + ? this._onionUrlbarRewritesAllowed + : this.docShell.onionUrlbarRewritesAllowed; + } + get charsetAutodetected() { return this.isRemoteBrowser ? this._charsetAutodetected @@ -1208,7 +1216,8 @@ aIsSynthetic, aHaveRequestContextID, aRequestContextID, - aContentType + aContentType, + aOnionUrlbarRewritesAllowed ) { if (this.isRemoteBrowser && this.messageManager) { if (aCharset != null) { @@ -1231,6 +1240,7 @@ this._contentRequestContextID = aHaveRequestContextID ? aRequestContextID : null; + this._onionUrlbarRewritesAllowed = aOnionUrlbarRewritesAllowed; } } @@ -1617,6 +1627,7 @@ "_contentPrincipal", "_contentPartitionedPrincipal", "_isSyntheticDocument", + "_onionUrlbarRewritesAllowed", ] ); } diff --git a/toolkit/modules/sessionstore/SessionHistory.jsm b/toolkit/modules/sessionstore/SessionHistory.jsm index f0a3e0e6653d..44ff3e988625 100644 --- a/toolkit/modules/sessionstore/SessionHistory.jsm +++ b/toolkit/modules/sessionstore/SessionHistory.jsm @@ -320,6 +320,7 @@ var SessionHistoryInternal = { } entry.persist = shEntry.persist; + entry.onionUrlbarRewritesAllowed = shEntry.onionUrlbarRewritesAllowed; return entry; }, @@ -614,6 +615,10 @@ var SessionHistoryInternal = { } } + if (entry.onionUrlbarRewritesAllowed) { + shEntry.onionUrlbarRewritesAllowed = entry.onionUrlbarRewritesAllowed; + } + return shEntry; },
1 0
0 0
[tor-browser/tor-browser-80.0b2-10.0-1] Bug 4234: Use the Firefox Update Process for Tor Browser.
by gk@torproject.org 17 Aug '20

17 Aug '20
commit 2c202f5dddeec9844721875efdd3d93f5766801d Author: Kathy Brade <brade(a)pearlcrescent.com> Date: Fri Jan 13 11:40:24 2017 -0500 Bug 4234: Use the Firefox Update Process for Tor Browser. The following files are never updated: TorBrowser/Data/Browser/profiles.ini TorBrowser/Data/Browser/profile.default/bookmarks.html TorBrowser/Data/Tor/torrc Mac OS: Store update metadata under TorBrowser/UpdateInfo. Removed the %OS_VERSION% component from the update URL (13047) and added support for minSupportedOSVersion, an attribute of the <update> element that may be used to trigger Firefox's "unsupported platform" behavior. Hide the "What's new" links (set app.releaseNotesURL value to about:blank). Windows: disable "runas" code path in updater (15201). Windows: avoid writing to the registry (16236). Also includes fixes for tickets 13047, 13301, 13356, 13594, 15406, 16014, 16909, 24476, and 25909. Also fix Bug 26049: reduce the delay before the update prompt is displayed. Instead of Firefox's 2 days, we use 1 hour (after which time the update doorhanger will be displayed). Also fix bug 27221: purge the startup cache if the Tor Browser version changed (even if the Firefox version and build ID did not change), e.g., after a minor Tor Browser update. Also fix 32616: Disable GetSecureOutputDirectoryPath() functionality. Bug 26048: potentially confusing "restart to update" message Within the update doorhanger, remove the misleading message that mentions that windows will be restored after an update is applied, and replace the "Restart and Restore" button label with an existing "Restart to update Tor Browser" string. Bug 28885: notify users that update is downloading Add a "Downloading Tor Browser update" item which appears in the hamburger (app) menu while the update service is downloading a MAR file. Before this change, the browser did not indicate to the user that an update was in progress, which is especially confusing in Tor Browser because downloads often take some time. If the user clicks on the new menu item, the about dialog is opened to allow the user to see download progress. As part of this fix, the update service was changed to always show update-related messages in the hamburger menu, even if the update was started in the foreground via the about dialog or via the "Check for Tor Browser Update" toolbar menu item. This change is consistent with the Tor Browser goal of making sure users are informed about the update process. Removed #28885 parts of this patch which have been uplifted to Firefox. --- browser/app/Makefile.in | 2 + browser/app/profile/000-tor-browser.js | 16 +- browser/app/profile/firefox.js | 10 +- .../base/content/aboutDialog-appUpdater-legacy.js | 2 +- browser/base/content/aboutDialog-appUpdater.js | 2 +- browser/base/content/aboutDialog.js | 12 +- browser/components/BrowserContentHandler.jsm | 39 ++- .../customizableui/content/panelUI.inc.xhtml | 5 +- browser/confvars.sh | 35 +-- browser/installer/package-manifest.in | 2 + build/application.ini.in | 2 +- build/moz.configure/init.configure | 3 +- config/createprecomplete.py | 17 +- .../client/aboutdebugging/src/actions/runtimes.js | 5 + toolkit/modules/UpdateUtils.jsm | 34 +-- toolkit/mozapps/extensions/AddonManager.jsm | 24 ++ toolkit/mozapps/extensions/test/browser/head.js | 1 + .../extensions/test/xpcshell/head_addons.js | 1 + toolkit/mozapps/update/UpdateService.jsm | 127 +++++++- toolkit/mozapps/update/UpdateServiceStub.jsm | 4 + toolkit/mozapps/update/common/updatehelper.cpp | 8 + toolkit/mozapps/update/moz.build | 5 +- toolkit/mozapps/update/updater/launchchild_osx.mm | 2 + toolkit/mozapps/update/updater/moz.build | 2 +- toolkit/mozapps/update/updater/updater.cpp | 339 ++++++++++++++++++--- toolkit/xre/MacLaunchHelper.h | 2 + toolkit/xre/MacLaunchHelper.mm | 2 + toolkit/xre/nsAppRunner.cpp | 22 +- toolkit/xre/nsUpdateDriver.cpp | 109 ++++++- toolkit/xre/nsXREDirProvider.cpp | 42 ++- tools/update-packaging/common.sh | 64 ++-- tools/update-packaging/make_full_update.sh | 38 +++ tools/update-packaging/make_incremental_update.sh | 141 ++++++++- 33 files changed, 963 insertions(+), 156 deletions(-) diff --git a/browser/app/Makefile.in b/browser/app/Makefile.in index 6770319f1866..7263b8e1dde7 100644 --- a/browser/app/Makefile.in +++ b/browser/app/Makefile.in @@ -98,10 +98,12 @@ tools repackage:: $(DIST)/bin/$(MOZ_APP_NAME) rsync -aL $(DIST)/bin/$(MOZ_APP_NAME) '$(dist_dest)/Contents/MacOS' cp -RL $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/firefox.icns '$(dist_dest)/Contents/Resources/firefox.icns' cp -RL $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/document.icns '$(dist_dest)/Contents/Resources/document.icns' +ifndef TOR_BROWSER_UPDATE $(MKDIR) -p '$(dist_dest)/Contents/Library/LaunchServices' ifdef MOZ_UPDATER mv -f '$(dist_dest)/Contents/MacOS/updater.app/Contents/MacOS/org.mozilla.updater' '$(dist_dest)/Contents/Library/LaunchServices' ln -s ../../../../Library/LaunchServices/org.mozilla.updater '$(dist_dest)/Contents/MacOS/updater.app/Contents/MacOS/org.mozilla.updater' +endif endif printf APPLTORB > '$(dist_dest)/Contents/PkgInfo' endif diff --git a/browser/app/profile/000-tor-browser.js b/browser/app/profile/000-tor-browser.js index 099f557a26f0..ab4cfba2ee50 100644 --- a/browser/app/profile/000-tor-browser.js +++ b/browser/app/profile/000-tor-browser.js @@ -7,7 +7,6 @@ // Disable initial homepage notifications pref("browser.search.update", false); pref("browser.rights.3.shown", true); -pref("browser.startup.homepage_override.mstone", "ignore"); pref("startup.homepage_welcome_url", ""); pref("startup.homepage_welcome_url.additional", ""); @@ -20,9 +19,17 @@ pref("startup.homepage_override_url", "https://blog.torproject.org/category/tags // Try to nag a bit more about updates: Pop up a restart dialog an hour after the initial dialog pref("app.update.promptWaitTime", 3600); - -#ifdef XP_WIN -// For now, disable staged updates on Windows (see #18292). +pref("app.update.notifyDuringDownload", true); +pref("app.update.url.manual", "https://www.torproject.org/download/languages/"); +pref("app.update.url.details", "https://www.torproject.org/download/"); +pref("app.update.badgeWaitTime", 0); +pref("app.releaseNotesURL", "about:blank"); + +#ifndef XP_MACOSX +// Disable staged updates on platforms other than macOS. +// Staged updates do not work on Windows due to #18292. +// Also, on Windows and Linux any changes that are made to the browser profile +// or Tor data after an update is staged will be lost. pref("app.update.staging.enabled", false); #endif @@ -82,6 +89,7 @@ pref("datareporting.policy.dataSubmissionEnabled", false); // Make sure Unified Telemetry is really disabled, see: #18738. pref("toolkit.telemetry.unified", false); pref("toolkit.telemetry.enabled", false); +pref("toolkit.telemetry.updatePing.enabled", false); // Make sure updater telemetry is disabled; see #25909. #ifdef XP_WIN // Defense-in-depth: ensure that the Windows default browser agent will // not ping Mozilla if it is somehow present (we omit it at build time). diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 00042c2532ad..bf68a249cf8d 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -136,14 +136,8 @@ pref("app.update.elevation.promptMaxAttempts", 2); pref("app.update.notifyDuringDownload", false); // If set to true, the Update Service will automatically download updates if the -// user can apply updates. This pref is no longer used on Windows, except as the -// default value to migrate to the new location that this data is now stored -// (which is in a file in the update directory). Because of this, this pref -// should no longer be used directly. Instead, getAppUpdateAutoEnabled and -// getAppUpdateAutoEnabled from UpdateUtils.jsm should be used. -#ifndef XP_WIN - pref("app.update.auto", true); -#endif +// user can apply updates. +pref("app.update.auto", true); // If set to true, the Update Service will apply updates in the background // when it finishes downloading them. diff --git a/browser/base/content/aboutDialog-appUpdater-legacy.js b/browser/base/content/aboutDialog-appUpdater-legacy.js index 0629e8eaf41a..9f96e2263295 100644 --- a/browser/base/content/aboutDialog-appUpdater-legacy.js +++ b/browser/base/content/aboutDialog-appUpdater-legacy.js @@ -192,7 +192,7 @@ appUpdater.prototype = { if (aChildID == "downloadAndInstall") { let updateVersion = gAppUpdater.update.displayVersion; // Include the build ID if this is an "a#" (nightly or aurora) build - if (/a\d+$/.test(updateVersion)) { + if (!AppConstants.TOR_BROWSER_UPDATE && /a\d+$/.test(updateVersion)) { let buildID = gAppUpdater.update.buildID; let year = buildID.slice(0, 4); let month = buildID.slice(4, 6); diff --git a/browser/base/content/aboutDialog-appUpdater.js b/browser/base/content/aboutDialog-appUpdater.js index 79828e5a279f..71de80626b89 100644 --- a/browser/base/content/aboutDialog-appUpdater.js +++ b/browser/base/content/aboutDialog-appUpdater.js @@ -147,7 +147,7 @@ appUpdater.prototype = { if (aChildID == "downloadAndInstall") { let updateVersion = gAppUpdater.update.displayVersion; // Include the build ID if this is an "a#" (nightly or aurora) build - if (/a\d+$/.test(updateVersion)) { + if (!AppConstants.TOR_BROWSER_UPDATE && /a\d+$/.test(updateVersion)) { let buildID = gAppUpdater.update.buildID; let year = buildID.slice(0, 4); let month = buildID.slice(4, 6); diff --git a/browser/base/content/aboutDialog.js b/browser/base/content/aboutDialog.js index 19f0d6f16c6c..13dfe0e37ca2 100644 --- a/browser/base/content/aboutDialog.js +++ b/browser/base/content/aboutDialog.js @@ -50,15 +50,13 @@ async function init(aEvent) { bits: Services.appinfo.is64Bit ? 64 : 32, }; + // Adjust version text to show the Tor Browser version + versionAttributes.version = AppConstants.TOR_BROWSER_VERSION + + " (based on Mozilla Firefox " + + AppConstants.MOZ_APP_VERSION_DISPLAY + ")"; + let version = Services.appinfo.version; if (/a\d+$/.test(version)) { - versionId = "aboutDialog-version-nightly"; - let buildID = Services.appinfo.appBuildID; - let year = buildID.slice(0, 4); - let month = buildID.slice(4, 6); - let day = buildID.slice(6, 8); - versionAttributes.isodate = `${year}-${month}-${day}`; - document.getElementById("experimental").hidden = false; document.getElementById("communityDesc").hidden = true; } diff --git a/browser/components/BrowserContentHandler.jsm b/browser/components/BrowserContentHandler.jsm index 88512149e320..39ae2e7f5e2d 100644 --- a/browser/components/BrowserContentHandler.jsm +++ b/browser/components/BrowserContentHandler.jsm @@ -46,6 +46,8 @@ XPCOMUtils.defineLazyGlobalGetters(this, [URL]); const NEWINSTALL_PAGE = "about:newinstall"; +const kTBSavedVersionPref = "browser.startup.homepage_override.torbrowser.version"; + // One-time startup homepage override configurations const ONCE_DOMAINS = ["mozilla.org", "firefox.com"]; const ONCE_PREF = "browser.startup.homepage_override.once"; @@ -105,7 +107,8 @@ const OVERRIDE_ALTERNATE_PROFILE = 4; * Returns: * OVERRIDE_NEW_PROFILE if this is the first run with a new profile. * OVERRIDE_NEW_MSTONE if this is the first run with a build with a different - * Gecko milestone (i.e. right after an upgrade). + * Gecko milestone or Tor Browser version (i.e. right + * after an upgrade). * OVERRIDE_NEW_BUILD_ID if this is the first run with a new build ID of the * same Gecko milestone (i.e. after a nightly upgrade). * OVERRIDE_NONE otherwise. @@ -128,6 +131,11 @@ function needHomepageOverride(prefb) { var mstone = Services.appinfo.platformVersion; + var savedTBVersion = null; + try { + savedTBVersion = prefb.getCharPref(kTBSavedVersionPref); + } catch (e) {} + var savedBuildID = prefb.getCharPref( "browser.startup.homepage_override.buildID", "" @@ -146,7 +154,22 @@ function needHomepageOverride(prefb) { prefb.setCharPref("browser.startup.homepage_override.mstone", mstone); prefb.setCharPref("browser.startup.homepage_override.buildID", buildID); - return savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE; + prefb.setCharPref(kTBSavedVersionPref, AppConstants.TOR_BROWSER_VERSION); + + // After an upgrade from an older release of Tor Browser (<= 5.5a1), the + // savedmstone will be undefined because those releases included the + // value "ignore" for the browser.startup.homepage_override.mstone pref. + // To correctly detect an upgrade vs. a new profile, we check for the + // presence of the "app.update.postupdate" pref. + let updated = prefb.prefHasUserValue("app.update.postupdate"); + return (savedmstone || updated) ? OVERRIDE_NEW_MSTONE + : OVERRIDE_NEW_PROFILE; + } + + if (AppConstants.TOR_BROWSER_VERSION != savedTBVersion) { + prefb.setCharPref("browser.startup.homepage_override.buildID", buildID); + prefb.setCharPref(kTBSavedVersionPref, AppConstants.TOR_BROWSER_VERSION); + return OVERRIDE_NEW_MSTONE; } if (buildID != savedBuildID) { @@ -645,6 +668,13 @@ nsBrowserContentHandler.prototype = { "browser.startup.homepage_override.buildID", "unknown" ); + + // We do the same for the Tor Browser version. + let old_tbversion = null; + try { + old_tbversion = prefb.getCharPref(kTBSavedVersionPref); + } catch (e) {} + override = needHomepageOverride(prefb); if (override != OVERRIDE_NONE) { switch (override) { @@ -677,9 +707,10 @@ nsBrowserContentHandler.prototype = { "startup.homepage_override_url" ); let update = UpdateManager.activeUpdate; + let old_version = old_tbversion ? old_tbversion: old_mstone; if ( update && - Services.vc.compare(update.appVersion, old_mstone) > 0 + Services.vc.compare(update.appVersion, old_version) > 0 ) { overridePage = getPostUpdateOverridePage(update, overridePage); // Send the update ping to signal that the update was successful. @@ -687,6 +718,8 @@ nsBrowserContentHandler.prototype = { } overridePage = overridePage.replace("%OLD_VERSION%", old_mstone); + overridePage = overridePage.replace("%OLD_TOR_BROWSER_VERSION%", + old_tbversion); break; case OVERRIDE_NEW_BUILD_ID: if (UpdateManager.activeUpdate) { diff --git a/browser/components/customizableui/content/panelUI.inc.xhtml b/browser/components/customizableui/content/panelUI.inc.xhtml index 8b326db72802..c84d5dd5c5a3 100644 --- a/browser/components/customizableui/content/panelUI.inc.xhtml +++ b/browser/components/customizableui/content/panelUI.inc.xhtml @@ -143,14 +143,15 @@ <popupnotification id="appMenu-update-restart-notification" popupid="update-restart" data-lazy-l10n-id="appmenu-update-restart" - data-l10n-attrs="buttonlabel, buttonaccesskey, secondarybuttonlabel, secondarybuttonaccesskey" + data-l10n-attrs="buttonaccesskey, secondarybuttonlabel, secondarybuttonaccesskey" + buttonlabel="&updateRestart.panelUI.label2;" closebuttonhidden="true" dropmarkerhidden="true" checkboxhidden="true" buttonhighlight="true" hidden="true"> <popupnotificationcontent id="update-restart-notification-content" orient="vertical"> - <description id="update-restart-description" data-lazy-l10n-id="appmenu-update-restart-message"></description> + <description id="update-restart-description">&#160;</description> </popupnotificationcontent> </popupnotification> diff --git a/browser/confvars.sh b/browser/confvars.sh index 31f28736606d..f994783f9c65 100755 --- a/browser/confvars.sh +++ b/browser/confvars.sh @@ -7,26 +7,6 @@ MOZ_APP_BASENAME=Firefox MOZ_APP_VENDOR=Mozilla MOZ_UPDATER=1 -if test "$OS_ARCH" = "WINNT"; then - if ! test "$HAVE_64BIT_BUILD"; then - if test "$MOZ_UPDATE_CHANNEL" = "nightly" -o \ - "$MOZ_UPDATE_CHANNEL" = "nightly-try" -o \ - "$MOZ_UPDATE_CHANNEL" = "aurora" -o \ - "$MOZ_UPDATE_CHANNEL" = "beta" -o \ - "$MOZ_UPDATE_CHANNEL" = "release"; then - if ! test "$MOZ_DEBUG"; then - if ! test "$USE_STUB_INSTALLER"; then - # Expect USE_STUB_INSTALLER from taskcluster for downstream task consistency - echo "ERROR: STUB installer expected to be enabled but" - echo "ERROR: USE_STUB_INSTALLER is not specified in the environment" - exit 1 - fi - MOZ_STUB_INSTALLER=1 - fi - fi - fi -fi - BROWSER_CHROME_URL=chrome://browser/content/browser.xhtml # MOZ_APP_DISPLAYNAME will be set by branding/configure.sh @@ -39,6 +19,21 @@ MOZ_BRANDING_DIRECTORY=browser/branding/unofficial MOZ_OFFICIAL_BRANDING_DIRECTORY=browser/branding/official MOZ_APP_ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384} +# ACCEPTED_MAR_CHANNEL_IDS should usually be the same as the value MAR_CHANNEL_ID. +# If more than one ID is needed, then you should use a comma separated list +# of values. +# The MAR_CHANNEL_ID must not contain the following 3 characters: ",\t " +if test "$MOZ_UPDATE_CHANNEL" = "alpha"; then + ACCEPTED_MAR_CHANNEL_IDS=torbrowser-torproject-alpha + MAR_CHANNEL_ID=torbrowser-torproject-alpha +elif test "$MOZ_UPDATE_CHANNEL" = "nightly"; then + ACCEPTED_MAR_CHANNEL_IDS=torbrowser-torproject-nightly + MAR_CHANNEL_ID=torbrowser-torproject-nightly +else + ACCEPTED_MAR_CHANNEL_IDS=torbrowser-torproject-release + MAR_CHANNEL_ID=torbrowser-torproject-release +fi + MOZ_PROFILE_MIGRATOR=1 # Include the DevTools client, not just the server (which is the default) diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 133da76ee066..67f961dcd6f4 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -36,8 +36,10 @@ ; Mac bundle stuff @APPNAME@/Contents/Info.plist #ifdef MOZ_UPDATER +#ifndef TOR_BROWSER_UPDATE @APPNAME@/Contents/Library/LaunchServices #endif +#endif @APPNAME@/Contents/PkgInfo @RESPATH@/firefox.icns @RESPATH@/document.icns diff --git a/build/application.ini.in b/build/application.ini.in index a6141de0be15..96faf3775588 100644 --- a/build/application.ini.in +++ b/build/application.ini.in @@ -52,5 +52,5 @@ ServerURL=https://crash-reports.mozilla.com/submit?id=@MOZ_APP_ID@&version=… #if MOZ_UPDATER [AppUpdate] -URL=https://@MOZ_APPUPDATE_HOST@/update/6/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%SYSTEM_CAPABILITIES%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml +URL=https://aus1.torproject.org/torbrowser/update_3/%CHANNEL%/%BUILD_TARGET%/%VERSION%/%LOCALE% #endif diff --git a/build/moz.configure/init.configure b/build/moz.configure/init.configure index 9345cb5e7f7d..7a1eeb98c848 100644 --- a/build/moz.configure/init.configure +++ b/build/moz.configure/init.configure @@ -1159,7 +1159,6 @@ def version_path(path): # set RELEASE_OR_BETA and NIGHTLY_BUILD variables depending on the cycle we're in # The logic works like this: # - if we have "a1" in GRE_MILESTONE, we're building Nightly (define NIGHTLY_BUILD) -# - otherwise, if we have "a" in GRE_MILESTONE, we're building Nightly or Aurora # - otherwise, we're building Release/Beta (define RELEASE_OR_BETA) @depends(check_build_environment, build_project, version_path, '--help') @imports(_from='__builtin__', _import='open') @@ -1206,7 +1205,7 @@ def milestone(build_env, build_project, version_path, _): if 'a1' in milestone: is_nightly = True - elif 'a' not in milestone: + else: is_release_or_beta = True major_version = milestone.split('.')[0] diff --git a/config/createprecomplete.py b/config/createprecomplete.py index fadd796aa21f..73793c8e8b19 100644 --- a/config/createprecomplete.py +++ b/config/createprecomplete.py @@ -5,6 +5,7 @@ # update instructions which is used to remove files and directories that are no # longer present in a complete update. The current working directory is used for # the location to enumerate and to create the precomplete file. +# For symlinks, remove instructions are always generated. from __future__ import absolute_import from __future__ import unicode_literals @@ -13,9 +14,17 @@ import os import io +# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms, +# we should remove all lines in this file that contain: +# TorBrowser/Data + def get_build_entries(root_path): """ Iterates through the root_path, creating a list for each file and directory. Excludes any file paths ending with channel-prefs.js. + To support Tor Browser updates, excludes: + TorBrowser/Data/Browser/profiles.ini + TorBrowser/Data/Browser/profile.default/bookmarks.html + TorBrowser/Data/Tor/torrc """ rel_file_path_set = set() rel_dir_path_set = set() @@ -26,6 +35,9 @@ def get_build_entries(root_path): rel_path_file = rel_path_file.replace("\\", "/") if not (rel_path_file.endswith("channel-prefs.js") or rel_path_file.endswith("update-settings.ini") or + rel_path_file == "TorBrowser/Data/Browser/profiles.ini" or + rel_path_file == "TorBrowser/Data/Browser/profile.default/bookmarks.html" or + rel_path_file == "TorBrowser/Data/Tor/torrc" or rel_path_file.find("distribution/") != -1): rel_file_path_set.add(rel_path_file) @@ -34,7 +46,10 @@ def get_build_entries(root_path): rel_path_dir = os.path.join(parent_dir_rel_path, dir_name) rel_path_dir = rel_path_dir.replace("\\", "/")+"/" if rel_path_dir.find("distribution/") == -1: - rel_dir_path_set.add(rel_path_dir) + if (os.path.islink(rel_path_dir[:-1])): + rel_file_path_set.add(rel_path_dir[:-1]) + else: + rel_dir_path_set.add(rel_path_dir) rel_file_path_list = list(rel_file_path_set) rel_file_path_list.sort(reverse=True) diff --git a/devtools/client/aboutdebugging/src/actions/runtimes.js b/devtools/client/aboutdebugging/src/actions/runtimes.js index 6ac28b6b76a8..5ed1b7904952 100644 --- a/devtools/client/aboutdebugging/src/actions/runtimes.js +++ b/devtools/client/aboutdebugging/src/actions/runtimes.js @@ -67,6 +67,11 @@ async function getRuntimeIcon(runtime, channel) { } } + // Use the release build skin for devtools within Tor Browser alpha releases. + if (channel === "alpha") { + return "chrome://devtools/skin/images/aboutdebugging-firefox-release.svg"; + } + return channel === "release" || channel === "beta" || channel === "aurora" ? `chrome://devtools/skin/images/aboutdebugging-firefox-${channel}.svg` : "chrome://devtools/skin/images/aboutdebugging-firefox-nightly.svg"; diff --git a/toolkit/modules/UpdateUtils.jsm b/toolkit/modules/UpdateUtils.jsm index d2b695b47442..883928472732 100644 --- a/toolkit/modules/UpdateUtils.jsm +++ b/toolkit/modules/UpdateUtils.jsm @@ -103,7 +103,7 @@ var UpdateUtils = { case "PRODUCT": return Services.appinfo.name; case "VERSION": - return Services.appinfo.version; + return AppConstants.TOR_BROWSER_VERSION; case "BUILD_ID": return Services.appinfo.appBuildID; case "BUILD_TARGET": @@ -167,25 +167,17 @@ var UpdateUtils = { * downloads and installs updates. This corresponds to whether or not the user * has selected "Automatically install updates" in about:preferences. * - * On Windows, this setting is shared across all profiles for the installation + * On Windows (except in Tor Browser), this setting is shared across all profiles + * for the installation * and is read asynchronously from the file. On other operating systems, this * setting is stored in a pref and is thus a per-profile setting. * * @return A Promise that resolves with a boolean. */ getAppUpdateAutoEnabled() { - if (Services.policies) { - if (!Services.policies.isAllowed("app-auto-updates-off")) { - // We aren't allowed to turn off auto-update - it is forced on. - return Promise.resolve(true); - } - if (!Services.policies.isAllowed("app-auto-updates-on")) { - // We aren't allowed to turn on auto-update - it is forced off. - return Promise.resolve(false); - } - } - if (AppConstants.platform != "win") { - // On platforms other than Windows the setting is stored in a preference. + if (AppConstants.TOR_BROWSER_UPDATE || (AppConstants.platform != "win")) { + // On platforms other than Windows and always in Tor Browser the setting + // is stored in a preference. let prefValue = Services.prefs.getBoolPref( PREF_APP_UPDATE_AUTO, DEFAULT_APP_UPDATE_AUTO @@ -256,7 +248,8 @@ var UpdateUtils = { * updates" and "Check for updates but let you choose to install them" options * in about:preferences. * - * On Windows, this setting is shared across all profiles for the installation + * On Windows (except in Tor Browser), this setting is shared across all profiles + * for the installation * and is written asynchronously to the file. On other operating systems, this * setting is stored in a pref and is thus a per-profile setting. * @@ -276,14 +269,9 @@ var UpdateUtils = { * this operation simply sets a pref. */ setAppUpdateAutoEnabled(enabledValue) { - if (this.appUpdateAutoSettingIsLocked()) { - return Promise.reject( - "setAppUpdateAutoEnabled: Unable to change value of setting because " + - "it is locked by policy" - ); - } - if (AppConstants.platform != "win") { - // Only in Windows do we store the update config in the update directory + if (AppConstants.TOR_BROWSER_UPDATE || (AppConstants.platform != "win")) { + // Only in Windows (but never for Tor Browser) do we store the update config + // in the update directory let prefValue = !!enabledValue; Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO, prefValue); // Rather than call maybeUpdateAutoConfigChanged, a pref observer has diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 2ef0232ab3bb..48876e3923e6 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -23,6 +23,7 @@ const { AppConstants } = ChromeUtils.import( const MOZ_COMPATIBILITY_NIGHTLY = ![ "aurora", + "alpha", "beta", "release", "esr", @@ -37,6 +38,7 @@ const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault"; const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; const PREF_SYS_ADDON_UPDATE_ENABLED = "extensions.systemAddon.update.enabled"; +const PREF_EM_LAST_TORBROWSER_VERSION = "extensions.lastTorBrowserVersion"; const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion"; @@ -682,6 +684,28 @@ var AddonManagerInternal = { ); } + // To ensure that extension and plugin code gets a chance to run + // after each browser update, set appChanged = true when the + // Tor Browser version has changed even if the Mozilla app + // version has not changed. + let tbChanged = undefined; + try { + tbChanged = AppConstants.TOR_BROWSER_VERSION != + Services.prefs.getCharPref(PREF_EM_LAST_TORBROWSER_VERSION); + } + catch (e) { } + if (tbChanged !== false) { + // Because PREF_EM_LAST_TORBROWSER_VERSION was not present in older + // versions of Tor Browser, an app change is indicated when tbChanged + // is undefined or true. + if (appChanged === false) { + appChanged = true; + } + + Services.prefs.setCharPref(PREF_EM_LAST_TORBROWSER_VERSION, + AppConstants.TOR_BROWSER_VERSION); + } + if (!MOZ_COMPATIBILITY_NIGHTLY) { PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + diff --git a/toolkit/mozapps/extensions/test/browser/head.js b/toolkit/mozapps/extensions/test/browser/head.js index b7bfd6275d34..587319d92ce4 100644 --- a/toolkit/mozapps/extensions/test/browser/head.js +++ b/toolkit/mozapps/extensions/test/browser/head.js @@ -43,6 +43,7 @@ var PREF_CHECK_COMPATIBILITY; var channel = Services.prefs.getCharPref("app.update.channel", "default"); if ( channel != "aurora" && + channel != "alpha" && channel != "beta" && channel != "release" && channel != "esr" diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index 1cc9df5a0783..bebeff6b6ee2 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -390,6 +390,7 @@ function isNightlyChannel() { return ( channel != "aurora" && + channel != "alpha" && channel != "beta" && channel != "release" && channel != "esr" diff --git a/toolkit/mozapps/update/UpdateService.jsm b/toolkit/mozapps/update/UpdateService.jsm index 6c7de1de0be4..e9953032e1a2 100644 --- a/toolkit/mozapps/update/UpdateService.jsm +++ b/toolkit/mozapps/update/UpdateService.jsm @@ -32,11 +32,15 @@ XPCOMUtils.defineLazyGlobalGetters(this, ["DOMParser", "XMLHttpRequest"]); XPCOMUtils.defineLazyModuleGetters(this, { AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm", CertUtils: "resource://gre/modules/CertUtils.jsm", +#ifdef XP_WIN ctypes: "resource://gre/modules/ctypes.jsm", +#endif DeferredTask: "resource://gre/modules/DeferredTask.jsm", OS: "resource://gre/modules/osfile.jsm", UpdateUtils: "resource://gre/modules/UpdateUtils.jsm", +#if !defined(TOR_BROWSER_UPDATE) WindowsRegistry: "resource://gre/modules/WindowsRegistry.jsm", +#endif }); const UPDATESERVICE_CID = Components.ID( @@ -296,6 +300,7 @@ function testWriteAccess(updateTestFile, createDirectory) { updateTestFile.remove(false); } +#ifdef XP_WIN /** * Windows only function that closes a Win32 handle. * @@ -388,6 +393,7 @@ function getPerInstallationMutexName(aGlobal = true) { (aGlobal ? "Global\\" : "") + "MozillaUpdateMutex-" + hasher.finish(true) ); } +#endif /** * Whether or not the current instance has the update mutex. The update mutex @@ -398,6 +404,7 @@ function getPerInstallationMutexName(aGlobal = true) { * @return true if this instance holds the update mutex */ function hasUpdateMutex() { +#ifdef XP_WIN if (AppConstants.platform != "win") { return true; } @@ -405,6 +412,9 @@ function hasUpdateMutex() { gUpdateMutexHandle = createMutex(getPerInstallationMutexName(true), false); } return !!gUpdateMutexHandle; +#else + return true; +#endif } /** @@ -435,6 +445,11 @@ function areDirectoryEntriesWriteable(aDir) { * @return true if elevation is required, false otherwise */ function getElevationRequired() { +#if defined(TOR_BROWSER_UPDATE) + // To avoid potential security holes associated with running the updater + // process with elevated privileges, Tor Browser does not support elevation. + return false; +#else if (AppConstants.platform != "macosx") { return false; } @@ -469,6 +484,7 @@ function getElevationRequired() { "not required" ); return false; +#endif } /** @@ -501,6 +517,7 @@ function getCanApplyUpdates() { return false; } +#if !defined(TOR_BROWSER_UPDATE) if (AppConstants.platform == "macosx") { LOG( "getCanApplyUpdates - bypass the write since elevation can be used " + @@ -516,6 +533,7 @@ function getCanApplyUpdates() { ); return true; } +#endif try { if (AppConstants.platform == "win") { @@ -1203,6 +1221,9 @@ function handleUpdateFailure(update, errorCode) { cancelations++; Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, cancelations); if (AppConstants.platform == "macosx") { +#if defined(TOR_BROWSER_UPDATE) + cleanupActiveUpdate(); +#else let osxCancelations = Services.prefs.getIntPref( PREF_APP_UPDATE_CANCELATIONS_OSX, 0 @@ -1226,6 +1247,7 @@ function handleUpdateFailure(update, errorCode) { (update.state = STATE_PENDING_ELEVATE) ); } +#endif update.statusText = gUpdateBundle.GetStringFromName("elevationFailure"); } else { writeStatusFile(getUpdatesDir(), (update.state = STATE_PENDING)); @@ -1752,7 +1774,26 @@ function Update(update) { this._patches.push(patch); } - if (!this._patches.length && !update.hasAttribute("unsupported")) { + if (update.hasAttribute("unsupported")) { + this.unsupported = ("true" == update.getAttribute("unsupported")); + } else if (update.hasAttribute("minSupportedOSVersion")) { + let minOSVersion = update.getAttribute("minSupportedOSVersion"); + try { + let osVersion = Services.sysinfo.getProperty("version"); + this.unsupported = (Services.vc.compare(osVersion, minOSVersion) < 0); + } catch (e) {} + } + if (!this.unsupported && update.hasAttribute("minSupportedInstructionSet")) { + let minInstructionSet = update.getAttribute("minSupportedInstructionSet"); + if (['MMX', 'SSE', 'SSE2', 'SSE3', + 'SSE4A', 'SSE4_1', 'SSE4_2'].indexOf(minInstructionSet) >= 0) { + try { + this.unsupported = !Services.sysinfo.getProperty("has" + minInstructionSet); + } catch (e) {} + } + } + + if (!this._patches.length && !this.unsupported) { throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE); } @@ -1790,9 +1831,7 @@ function Update(update) { if (!isNaN(attr.value)) { this.promptWaitTime = parseInt(attr.value); } - } else if (attr.name == "unsupported") { - this.unsupported = attr.value == "true"; - } else { + } else if (attr.name != "unsupported") { switch (attr.name) { case "appVersion": case "buildID": @@ -1817,7 +1856,11 @@ function Update(update) { } if (!this.previousAppVersion) { +#ifdef TOR_BROWSER_UPDATE + this.previousAppVersion = AppConstants.TOR_BROWSER_VERSION; +#else this.previousAppVersion = Services.appinfo.version; +#endif } if (!this.elevationFailure) { @@ -2200,6 +2243,7 @@ UpdateService.prototype = { Services.obs.removeObserver(this, topic); Services.prefs.removeObserver(PREF_APP_UPDATE_LOG, this); +#ifdef XP_WIN if (AppConstants.platform == "win" && gUpdateMutexHandle) { // If we hold the update mutex, let it go! // The OS would clean this up sometime after shutdown, @@ -2207,6 +2251,7 @@ UpdateService.prototype = { closeHandle(gUpdateMutexHandle); gUpdateMutexHandle = null; } +#endif if (this._retryTimer) { this._retryTimer.cancel(); } @@ -2240,6 +2285,7 @@ UpdateService.prototype = { } break; case "test-close-handle-update-mutex": +#ifdef XP_WIN if (Cu.isInAutomation) { if (AppConstants.platform == "win" && gUpdateMutexHandle) { LOG("UpdateService:observe - closing mutex handle for testing"); @@ -2247,6 +2293,7 @@ UpdateService.prototype = { gUpdateMutexHandle = null; } } +#endif break; } }, @@ -2268,6 +2315,9 @@ UpdateService.prototype = { */ _postUpdateProcessing: function AUS__postUpdateProcessing() { gUpdateFileWriteInfo = { phase: "startup", failure: false }; +#if defined(TOR_BROWSER_UPDATE) && !defined(XP_MACOSX) + this._removeOrphanedTorBrowserFiles(); +#endif if (!this.canCheckForUpdates) { LOG( "UpdateService:_postUpdateProcessing - unable to check for " + @@ -2504,6 +2554,42 @@ UpdateService.prototype = { } }, +#if defined(TOR_BROWSER_UPDATE) && !defined(XP_MACOSX) + /** + * When updating from an earlier version to Tor Browser 6.0 or later, old + * update info files are left behind on Linux and Windows. Remove them. + */ + _removeOrphanedTorBrowserFiles: function AUS__removeOrphanedTorBrowserFiles() { + try { + let oldUpdateInfoDir = getAppBaseDir(); // aka the Browser directory. + +#ifdef XP_WIN + // On Windows, the updater files were stored under + // Browser/TorBrowser/Data/Browser/Caches/firefox/ + oldUpdateInfoDir.appendRelativePath( + "TorBrowser\\Data\\Browser\\Caches\\firefox"); +#endif + + // Remove the updates directory. + let updatesDir = oldUpdateInfoDir.clone(); + updatesDir.append("updates"); + if (updatesDir.exists() && updatesDir.isDirectory()) { + updatesDir.remove(true); + } + + // Remove files: active-update.xml and updates.xml + let filesToRemove = [ "active-update.xml", "updates.xml" ]; + filesToRemove.forEach(function(aFileName) { + let f = oldUpdateInfoDir.clone(); + f.append(aFileName); + if (f.exists()) { + f.remove(false); + } + }); + } catch (e) {} + }, +#endif + /** * Register an observer when the network comes online, so we can short-circuit * the app.update.interval when there isn't connectivity @@ -2859,9 +2945,14 @@ UpdateService.prototype = { updates.forEach(function(aUpdate) { // Ignore updates for older versions of the application and updates for // the same version of the application with the same build ID. - if ( - vc.compare(aUpdate.appVersion, Services.appinfo.version) < 0 || - (vc.compare(aUpdate.appVersion, Services.appinfo.version) == 0 && +#ifdef TOR_BROWSER_UPDATE + let compatVersion = AppConstants.TOR_BROWSER_VERSION; +#else + let compatVersion = Services.appinfo.version; +#endif + let rc = vc.compare(aUpdate.appVersion, compatVersion); + if (rc < 0 || + (rc == 0 && aUpdate.buildID == Services.appinfo.appBuildID) ) { LOG( @@ -3214,20 +3305,32 @@ UpdateService.prototype = { // current application's version or the update's version is the same as the // application's version and the build ID is the same as the application's // build ID. +#ifdef TOR_BROWSER_UPDATE + let compatVersion = AppConstants.TOR_BROWSER_VERSION; +#else + let compatVersion = Services.appinfo.version; +#endif if ( update.appVersion && - (Services.vc.compare(update.appVersion, Services.appinfo.version) < 0 || + (Services.vc.compare(update.appVersion, compatVersion) < 0 || (update.buildID && update.buildID == Services.appinfo.appBuildID && - update.appVersion == Services.appinfo.version)) + update.appVersion == compatVersion)) ) { LOG( "UpdateService:downloadUpdate - canceling download of update since " + "it is for an earlier or same application version and build ID.\n" + +#ifdef TOR_BROWSER_UPDATE + "current Tor Browser version: " + + compatVersion + + "\n" + + "update Tor Browser version : " + +#else "current application version: " + - Services.appinfo.version + + compatVersion + "\n" + "update application version : " + +#endif update.appVersion + "\n" + "current build ID: " + @@ -3793,6 +3896,7 @@ Checker.prototype = { */ _callback: null, +#if !defined(TOR_BROWSER_UPDATE) _getCanMigrate: function UC__getCanMigrate() { if (AppConstants.platform != "win") { return false; @@ -3862,6 +3966,7 @@ Checker.prototype = { LOG("Checker:_getCanMigrate - no registry entries for this installation"); return false; }, +#endif // !defined(TOR_BROWSER_UPDATE) /** * The URL of the update service XML file to connect to that contains details @@ -3890,9 +3995,11 @@ Checker.prototype = { url += (url.includes("?") ? "&" : "?") + "force=1"; } +#if !defined(TOR_BROWSER_UPDATE) if (this._getCanMigrate()) { url += (url.includes("?") ? "&" : "?") + "mig64=1"; } +#endif LOG("Checker:getUpdateURL - update URL: " + url); return url; diff --git a/toolkit/mozapps/update/UpdateServiceStub.jsm b/toolkit/mozapps/update/UpdateServiceStub.jsm index 3535131daa28..3ecfd97c8778 100644 --- a/toolkit/mozapps/update/UpdateServiceStub.jsm +++ b/toolkit/mozapps/update/UpdateServiceStub.jsm @@ -78,8 +78,12 @@ function UpdateServiceStub() { // contains the status file's path // We may need to migrate update data + // In Tor Browser we skip this because we do not use an update agent and we + // do not want to store any data outside of the browser installation directory. + // For more info, see https://bugzilla.mozilla.org/show_bug.cgi?id=1458314 if ( AppConstants.platform == "win" && + !AppConstants.TOR_BROWSER_UPDATE && !Services.prefs.getBoolPref(prefUpdateDirMigrated, false) ) { migrateUpdateDirectory(); diff --git a/toolkit/mozapps/update/common/updatehelper.cpp b/toolkit/mozapps/update/common/updatehelper.cpp index b094d9eb75e9..c825d3c1ea8e 100644 --- a/toolkit/mozapps/update/common/updatehelper.cpp +++ b/toolkit/mozapps/update/common/updatehelper.cpp @@ -66,6 +66,13 @@ BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath, * @return TRUE if successful */ BOOL GetSecureOutputDirectoryPath(LPWSTR outBuf) { +#ifdef TOR_BROWSER_UPDATE + // This function is used to support the maintenance service and elevated + // updates and is therefore not called by Tor Browser's updater. We stub + // it out to avoid any chance that the Tor Browser updater will create + // files under C:\Program Files (x86)\ or a similar location. + return FALSE; +#else PWSTR progFilesX86; if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, KF_FLAG_CREATE, nullptr, &progFilesX86))) { @@ -99,6 +106,7 @@ BOOL GetSecureOutputDirectoryPath(LPWSTR outBuf) { } return TRUE; +#endif } /** diff --git a/toolkit/mozapps/update/moz.build b/toolkit/mozapps/update/moz.build index ddfda4571c31..d0c93895c1ec 100644 --- a/toolkit/mozapps/update/moz.build +++ b/toolkit/mozapps/update/moz.build @@ -22,11 +22,14 @@ EXTRA_COMPONENTS += [ EXTRA_JS_MODULES += [ 'UpdateListener.jsm', - 'UpdateService.jsm', 'UpdateServiceStub.jsm', 'UpdateTelemetry.jsm', ] +EXTRA_PP_JS_MODULES += [ + 'UpdateService.jsm', +] + XPCOM_MANIFESTS += [ 'components.conf', ] diff --git a/toolkit/mozapps/update/updater/launchchild_osx.mm b/toolkit/mozapps/update/updater/launchchild_osx.mm index 95c39f3d0e09..37677e8899f0 100644 --- a/toolkit/mozapps/update/updater/launchchild_osx.mm +++ b/toolkit/mozapps/update/updater/launchchild_osx.mm @@ -254,6 +254,7 @@ bool ObtainUpdaterArguments(int* argc, char*** argv) { @end +#ifndef TOR_BROWSER_UPDATE bool ServeElevatedUpdate(int argc, const char** argv) { MacAutoreleasePool pool; @@ -269,6 +270,7 @@ bool ServeElevatedUpdate(int argc, const char** argv) { [updater release]; return didSucceed; } +#endif bool IsOwnedByGroupAdmin(const char* aAppBundle) { MacAutoreleasePool pool; diff --git a/toolkit/mozapps/update/updater/moz.build b/toolkit/mozapps/update/updater/moz.build index a0eff9a3b7cd..ecc634c87037 100644 --- a/toolkit/mozapps/update/updater/moz.build +++ b/toolkit/mozapps/update/updater/moz.build @@ -49,7 +49,7 @@ xpcshell_cert.script = 'gen_cert_header.py:create_header' dep1_cert.script = 'gen_cert_header.py:create_header' dep2_cert.script = 'gen_cert_header.py:create_header' -if CONFIG['MOZ_UPDATE_CHANNEL'] in ('beta', 'release', 'esr'): +if CONFIG['MOZ_UPDATE_CHANNEL'] in ('alpha', 'beta', 'release', 'esr'): primary_cert.inputs += ['release_primary.der'] secondary_cert.inputs += ['release_secondary.der'] elif CONFIG['MOZ_UPDATE_CHANNEL'] in ('nightly', 'aurora', 'nightly-elm', diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp index 02e35ed81aa2..6e2e3a356b09 100644 --- a/toolkit/mozapps/update/updater/updater.cpp +++ b/toolkit/mozapps/update/updater/updater.cpp @@ -16,7 +16,7 @@ * updatev3.manifest * ----------------- * method = "add" | "add-if" | "add-if-not" | "patch" | "patch-if" | - * "remove" | "rmdir" | "rmrfdir" | type + * "remove" | "rmdir" | "rmrfdir" | "addsymlink" | type * * 'add-if-not' adds a file if it doesn't exist. * @@ -78,7 +78,9 @@ bool IsRecursivelyWritable(const char* aPath); void LaunchChild(int argc, const char** argv); void LaunchMacPostProcess(const char* aAppBundle); bool ObtainUpdaterArguments(int* argc, char*** argv); +# ifndef TOR_BROWSER_UPDATE bool ServeElevatedUpdate(int argc, const char** argv); +# endif void SetGroupOwnershipAndPermissions(const char* aAppBundle); struct UpdateServerThreadArgs { int argc; @@ -478,9 +480,12 @@ static const NS_tchar* get_relative_path(const NS_tchar* fullpath) { * The line from the manifest that contains the path. * @param isdir * Whether the path is a directory path. Defaults to false. + * @param islinktarget + * Whether the path is a symbolic link target. Defaults to false. * @return valid filesystem path or nullptr if the path checks fail. */ -static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false) { +static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false, + bool islinktarget = false) { NS_tchar* path = mstrtok(kQuote, line); if (!path) { LOG(("get_valid_path: unable to determine path: " LOG_S, *line)); @@ -516,10 +521,12 @@ static NS_tchar* get_valid_path(NS_tchar** line, bool isdir = false) { path[NS_tstrlen(path) - 1] = NS_T('\0'); } - // Don't allow relative paths that resolve to a parent directory. - if (NS_tstrstr(path, NS_T("..")) != nullptr) { - LOG(("get_valid_path: paths must not contain '..': " LOG_S, path)); - return nullptr; + if (!islinktarget) { + // Don't allow relative paths that resolve to a parent directory. + if (NS_tstrstr(path, NS_T("..")) != nullptr) { + LOG(("get_valid_path: paths must not contain '..': " LOG_S, path)); + return nullptr; + } } return path; @@ -559,7 +566,7 @@ static void ensure_write_permissions(const NS_tchar* path) { (void)_wchmod(path, _S_IREAD | _S_IWRITE); #else struct stat fs; - if (!stat(path, &fs) && !(fs.st_mode & S_IWUSR)) { + if (!lstat(path, &fs) && !S_ISLNK(fs.st_mode) && !(fs.st_mode & S_IWUSR)) { (void)chmod(path, fs.st_mode | S_IWUSR); } #endif @@ -746,11 +753,9 @@ static int ensure_copy(const NS_tchar* path, const NS_tchar* dest) { return READ_ERROR; } -# ifdef XP_UNIX if (S_ISLNK(ss.st_mode)) { return ensure_copy_symlink(path, dest); } -# endif AutoFile infile(ensure_open(path, NS_T("rb"), ss.st_mode)); if (!infile) { @@ -837,12 +842,19 @@ static int ensure_copy_recursive(const NS_tchar* path, const NS_tchar* dest, return READ_ERROR; } -#ifdef XP_UNIX +#ifndef XP_WIN if (S_ISLNK(sInfo.st_mode)) { return ensure_copy_symlink(path, dest); } #endif +#ifdef XP_UNIX + // Ignore Unix domain sockets. See #20691. + if (S_ISSOCK(sInfo.st_mode)) { + return 0; + } +#endif + if (!S_ISDIR(sInfo.st_mode)) { return ensure_copy(path, dest); } @@ -899,7 +911,7 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath, } struct NS_tstat_t spathInfo; - rv = NS_tstat(spath, &spathInfo); + rv = NS_tlstat(spath, &spathInfo); // Get info about file or symlink. if (rv) { LOG(("rename_file: failed to read file status info: " LOG_S ", " "err: %d", @@ -907,7 +919,12 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath, return READ_ERROR; } - if (!S_ISREG(spathInfo.st_mode)) { +#ifdef XP_WIN + if (!S_ISREG(spathInfo.st_mode)) +#else + if (!S_ISREG(spathInfo.st_mode) && !S_ISLNK(spathInfo.st_mode)) +#endif + { if (allowDirs && !S_ISDIR(spathInfo.st_mode)) { LOG(("rename_file: path present, but not a file: " LOG_S ", err: %d", spath, errno)); @@ -916,7 +933,12 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath, LOG(("rename_file: proceeding to rename the directory")); } - if (!NS_taccess(dpath, F_OK)) { +#ifdef XP_WIN + if (!NS_taccess(dpath, F_OK)) +#else + if (!S_ISLNK(spathInfo.st_mode) && !NS_taccess(dpath, F_OK)) +#endif + { if (ensure_remove(dpath)) { LOG( ("rename_file: destination file exists and could not be " @@ -936,7 +958,7 @@ static int rename_file(const NS_tchar* spath, const NS_tchar* dpath, return OK; } -#ifdef XP_WIN +#if defined(XP_WIN) && !defined(TOR_BROWSER_UPDATE) // Remove the directory pointed to by path and all of its files and // sub-directories. If a file is in use move it to the tobedeleted directory // and attempt to schedule removal of the file on reboot @@ -1035,7 +1057,19 @@ static int backup_restore(const NS_tchar* path, const NS_tchar* relPath) { NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]), NS_T("%s") BACKUP_EXT, relPath); - if (NS_taccess(backup, F_OK)) { + bool isLink = false; +#ifndef XP_WIN + struct stat linkInfo; + int rv = lstat(backup, &linkInfo); + if (rv) { + LOG(("backup_restore: cannot get info for backup file: " LOG_S ", err: %d", + relBackup, errno)); + return OK; + } + isLink = S_ISLNK(linkInfo.st_mode); +#endif + + if (!isLink && NS_taccess(backup, F_OK)) { LOG(("backup_restore: backup file doesn't exist: " LOG_S, relBackup)); return OK; } @@ -1053,8 +1087,18 @@ static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) { NS_tsnprintf(relBackup, sizeof(relBackup) / sizeof(relBackup[0]), NS_T("%s") BACKUP_EXT, relPath); + bool isLink = false; +#ifndef XP_WIN + struct stat linkInfo; + int rv2 = lstat(backup, &linkInfo); + if (rv2) { + return OK; // File does not exist; nothing to do. + } + isLink = S_ISLNK(linkInfo.st_mode); +#endif + // Nothing to discard - if (NS_taccess(backup, F_OK)) { + if (!isLink && NS_taccess(backup, F_OK)) { return OK; } @@ -1069,6 +1113,8 @@ static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) { relBackup, relPath)); return WRITE_ERROR_DELETE_BACKUP; } + +# if !defined(TOR_BROWSER_UPDATE) // The MoveFileEx call to remove the file on OS reboot will fail if the // process doesn't have write access to the HKEY_LOCAL_MACHINE registry key // but this is ok since the installer / uninstaller will delete the @@ -1085,6 +1131,7 @@ static int backup_discard(const NS_tchar* path, const NS_tchar* relPath) { "file: " LOG_S, relPath)); } +# endif } #else if (rv) { @@ -1139,7 +1186,7 @@ class Action { class RemoveFile : public Action { public: - RemoveFile() : mSkip(0) {} + RemoveFile() : mSkip(0), mIsLink(0) {} int Parse(NS_tchar* line) override; int Prepare() override; @@ -1150,6 +1197,7 @@ class RemoveFile : public Action { mozilla::UniquePtr<NS_tchar[]> mFile; mozilla::UniquePtr<NS_tchar[]> mRelPath; int mSkip; + int mIsLink; }; int RemoveFile::Parse(NS_tchar* line) { @@ -1172,28 +1220,39 @@ int RemoveFile::Parse(NS_tchar* line) { } int RemoveFile::Prepare() { - // Skip the file if it already doesn't exist. - int rv = NS_taccess(mFile.get(), F_OK); - if (rv) { - mSkip = 1; - mProgressCost = 0; - return OK; + int rv; +#ifndef XP_WIN + struct stat linkInfo; + rv = lstat(mFile.get(), &linkInfo); + mIsLink = ((0 == rv) && S_ISLNK(linkInfo.st_mode)); +#endif + + if (!mIsLink) { + // Skip the file if it already doesn't exist. + rv = NS_taccess(mFile.get(), F_OK); + if (rv) { + mSkip = 1; + mProgressCost = 0; + return OK; + } } LOG(("PREPARE REMOVEFILE " LOG_S, mRelPath.get())); - // Make sure that we're actually a file... - struct NS_tstat_t fileInfo; - rv = NS_tstat(mFile.get(), &fileInfo); - if (rv) { - LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(), - errno)); - return READ_ERROR; - } + if (!mIsLink) { + // Make sure that we're actually a file... + struct NS_tstat_t fileInfo; + rv = NS_tstat(mFile.get(), &fileInfo); + if (rv) { + LOG(("failed to read file status info: " LOG_S ", err: %d", mFile.get(), + errno)); + return READ_ERROR; + } - if (!S_ISREG(fileInfo.st_mode)) { - LOG(("path present, but not a file: " LOG_S, mFile.get())); - return DELETE_ERROR_EXPECTED_FILE; + if (!S_ISREG(fileInfo.st_mode)) { + LOG(("path present, but not a file: " LOG_S, mFile.get())); + return DELETE_ERROR_EXPECTED_FILE; + } } NS_tchar* slash = (NS_tchar*)NS_tstrrchr(mFile.get(), NS_T('/')); @@ -1222,7 +1281,13 @@ int RemoveFile::Execute() { // The file is checked for existence here and in Prepare since it might have // been removed by a separate instruction: bug 311099. - int rv = NS_taccess(mFile.get(), F_OK); + int rv = 0; + if (mIsLink) { + struct NS_tstat_t linkInfo; + rv = NS_tlstat(mFile.get(), &linkInfo); + } else { + rv = NS_taccess(mFile.get(), F_OK); + } if (rv) { LOG(("file cannot be removed because it does not exist; skipping")); mSkip = 1; @@ -1945,6 +2010,92 @@ void PatchIfFile::Finish(int status) { PatchFile::Finish(status); } +#ifndef XP_WIN +class AddSymlink : public Action { + public: + AddSymlink() : mAdded(false) {} + + virtual int Parse(NS_tchar* line); + virtual int Prepare(); + virtual int Execute(); + virtual void Finish(int status); + + private: + mozilla::UniquePtr<NS_tchar[]> mLinkPath; + mozilla::UniquePtr<NS_tchar[]> mRelPath; + mozilla::UniquePtr<NS_tchar[]> mTarget; + bool mAdded; +}; + +int AddSymlink::Parse(NS_tchar* line) { + // format "<linkname>" "target" + + NS_tchar* validPath = get_valid_path(&line); + if (!validPath) return PARSE_ERROR; + + mRelPath = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN); + NS_tstrcpy(mRelPath.get(), validPath); + mLinkPath.reset(get_full_path(validPath)); + if (!mLinkPath) { + return PARSE_ERROR; + } + + // consume whitespace between args + NS_tchar* q = mstrtok(kQuote, &line); + if (!q) return PARSE_ERROR; + + validPath = get_valid_path(&line, false, true); + if (!validPath) return PARSE_ERROR; + + mTarget = mozilla::MakeUnique<NS_tchar[]>(MAXPATHLEN); + NS_tstrcpy(mTarget.get(), validPath); + + return OK; +} + +int AddSymlink::Prepare() { + LOG(("PREPARE ADDSYMLINK " LOG_S " -> " LOG_S, mRelPath.get(), + mTarget.get())); + + return OK; +} + +int AddSymlink::Execute() { + LOG(("EXECUTE ADDSYMLINK " LOG_S " -> " LOG_S, mRelPath.get(), + mTarget.get())); + + // First make sure that we can actually get rid of any existing file or link. + struct stat linkInfo; + int rv = lstat(mLinkPath.get(), &linkInfo); + if ((0 == rv) && !S_ISLNK(linkInfo.st_mode)) { + rv = NS_taccess(mLinkPath.get(), F_OK); + } + if (rv == 0) { + rv = backup_create(mLinkPath.get()); + if (rv) return rv; + } else { + rv = ensure_parent_dir(mLinkPath.get()); + if (rv) return rv; + } + + // Create the link. + rv = symlink(mTarget.get(), mLinkPath.get()); + if (!rv) { + mAdded = true; + } + + return rv; +} + +void AddSymlink::Finish(int status) { + LOG(("FINISH ADDSYMLINK " LOG_S " -> " LOG_S, mRelPath.get(), mTarget.get())); + // When there is an update failure and a link has been added it is removed + // here since there might not be a backup to replace it. + if (status && mAdded) NS_tremove(mLinkPath.get()); + backup_finish(mLinkPath.get(), mRelPath.get(), status); +} +#endif + //----------------------------------------------------------------------------- #ifdef XP_WIN @@ -2282,14 +2433,29 @@ static bool IsSecureUpdateStatusSucceeded(bool& isSucceeded) { */ static int CopyInstallDirToDestDir() { // These files should not be copied over to the updated app -#ifdef XP_WIN -# define SKIPLIST_COUNT 3 -#elif XP_MACOSX -# define SKIPLIST_COUNT 0 +#if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) +# ifdef XP_WIN +# define SKIPLIST_COUNT 6 +# else +# define SKIPLIST_COUNT 5 +# endif #else -# define SKIPLIST_COUNT 2 +# ifdef XP_WIN +# define SKIPLIST_COUNT 3 +# elif XP_MACOSX +# define SKIPLIST_COUNT 0 +# else +# define SKIPLIST_COUNT 2 +# endif #endif copy_recursive_skiplist<SKIPLIST_COUNT> skiplist; +#if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) +# ifdef XP_MACOSX + skiplist.append(0, gInstallDirPath, NS_T("Updated.app")); + skiplist.append(1, gInstallDirPath, NS_T("TorBrowser/UpdateInfo/updates/0")); +# endif +#endif + #ifndef XP_MACOSX skiplist.append(0, gInstallDirPath, NS_T("updated")); skiplist.append(1, gInstallDirPath, NS_T("updates/0")); @@ -2298,6 +2464,19 @@ static int CopyInstallDirToDestDir() { # endif #endif +#if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) +# ifdef XP_WIN + skiplist.append(SKIPLIST_COUNT - 3, gInstallDirPath, + NS_T("TorBrowser/Data/Browser/profile.default/parent.lock")); +# else + skiplist.append(SKIPLIST_COUNT - 3, gInstallDirPath, + NS_T("TorBrowser/Data/Browser/profile.default/.parentlock")); +# endif + + skiplist.append(SKIPLIST_COUNT - 1, gInstallDirPath, + NS_T("TorBrowser/Data/Tor/lock")); +#endif + return ensure_copy_recursive(gInstallDirPath, gWorkingDirPath, skiplist); } @@ -2435,7 +2614,9 @@ static int ProcessReplaceRequest() { if (NS_taccess(deleteDir, F_OK)) { NS_tmkdir(deleteDir, 0755); } +# if !defined(TOR_BROWSER_UPDATE) remove_recursive_on_reboot(tmpDir, deleteDir); +# endif #endif } @@ -2443,8 +2624,45 @@ static int ProcessReplaceRequest() { // On OS X, we we need to remove the staging directory after its Contents // directory has been moved. NS_tchar updatedAppDir[MAXPATHLEN]; +# if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) + NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir) / sizeof(updatedAppDir[0]), + NS_T("%s/Updated.app"), gInstallDirPath); + // For Tor Browser on OS X, we also need to copy everything else that is + // inside Updated.app. + NS_tDIR* dir = NS_topendir(updatedAppDir); + if (dir) { + NS_tdirent* entry; + while ((entry = NS_treaddir(dir)) != 0) { + if (NS_tstrcmp(entry->d_name, NS_T(".")) && + NS_tstrcmp(entry->d_name, NS_T(".."))) { + NS_tchar childSrcPath[MAXPATHLEN]; + NS_tsnprintf(childSrcPath, + sizeof(childSrcPath) / sizeof(childSrcPath[0]), + NS_T("%s/%s"), updatedAppDir, entry->d_name); + NS_tchar childDstPath[MAXPATHLEN]; + NS_tsnprintf(childDstPath, + sizeof(childDstPath) / sizeof(childDstPath[0]), + NS_T("%s/%s"), gInstallDirPath, entry->d_name); + ensure_remove_recursive(childDstPath); + rv = rename_file(childSrcPath, childDstPath, true); + if (rv) { + LOG(("Moving " LOG_S " to " LOG_S " failed, err: %d", childSrcPath, + childDstPath, errno)); + } + } + } + + NS_tclosedir(dir); + } else { + LOG(("Updated.app dir can't be found: " LOG_S ", err: %d", updatedAppDir, + errno)); + } +# else NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir) / sizeof(updatedAppDir[0]), NS_T("%s/Updated.app"), gPatchDirPath); +# endif + + // Remove the Updated.app directory. ensure_remove_recursive(updatedAppDir); #endif @@ -2618,11 +2836,15 @@ static void UpdateThreadFunc(void* param) { #ifdef XP_MACOSX static void ServeElevatedUpdateThreadFunc(void* param) { +# ifdef TOR_BROWSER_UPDATE + WriteStatusFile(ELEVATION_CANCELED); +# else UpdateServerThreadArgs* threadArgs = (UpdateServerThreadArgs*)param; gSucceeded = ServeElevatedUpdate(threadArgs->argc, threadArgs->argv); if (!gSucceeded) { WriteStatusFile(ELEVATION_CANCELED); } +# endif QuitProgressUI(); } @@ -2646,7 +2868,7 @@ int LaunchCallbackAndPostProcessApps(int argc, NS_tchar** argv, #endif ) { if (argc > callbackIndex) { -#if defined(XP_WIN) +#if defined(XP_WIN) && !defined(TOR_BROWSER_UPDATE) if (gSucceeded) { if (!LaunchWinPostProcess(gInstallDirPath, gPatchDirPath)) { fprintf(stderr, "The post update process was not launched"); @@ -2701,8 +2923,12 @@ int NS_main(int argc, NS_tchar** argv) { UmaskContext umaskContext(0); bool isElevated = +# ifdef TOR_BROWSER_UPDATE + false; +# else strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") != 0; +# endif if (isElevated) { if (!ObtainUpdaterArguments(&argc, &argv)) { // Won't actually get here because ObtainUpdaterArguments will terminate @@ -3346,6 +3572,26 @@ int NS_main(int argc, NS_tchar** argv) { // using the service is because we are testing. if (!useService && !noServiceFallback && updateLockFileHandle == INVALID_HANDLE_VALUE) { +# ifdef TOR_BROWSER_UPDATE +# ifdef TOR_BROWSER_DATA_OUTSIDE_APP_DIR + // Because the TorBrowser-Data directory that contains the user's + // profile is a sibling of the Tor Browser installation directory, + // the user probably has permission to apply updates. Therefore, to + // avoid potential security issues such as CVE-2015-0833, do not + // attempt to elevate privileges. Instead, write a "failed" message + // to the update status file (this function will return immediately + // after the CloseHandle(elevatedFileHandle) call below). +# else + // Because the user profile is contained within the Tor Browser + // installation directory, the user almost certainly has permission to + // apply updates. Therefore, to avoid potential security issues such + // as CVE-2015-0833, do not attempt to elevate privileges. Instead, + // write a "failed" message to the update status file (this function + // will return immediately after the CloseHandle(elevatedFileHandle) + // call below). +# endif + WriteStatusFile(WRITE_ERROR_ACCESS_DENIED); +# else // Get the secure ID before trying to update so it is possible to // determine if the updater has created a new one. char uuidStringBefore[UUID_LEN] = {'\0'}; @@ -3391,6 +3637,7 @@ int NS_main(int argc, NS_tchar** argv) { gCopyOutputFiles = false; WriteStatusFile(ELEVATION_CANCELED); } +# endif } // Note: The PostUpdate process is launched by the elevated updater which @@ -3725,6 +3972,7 @@ int NS_main(int argc, NS_tchar** argv) { if (!sStagedUpdate && !sReplaceRequest && _wrmdir(gDeleteDirPath)) { LOG(("NS_main: unable to remove directory: " LOG_S ", err: %d", DELETE_DIR, errno)); +# if !defined(TOR_BROWSER_UPDATE) // The directory probably couldn't be removed due to it containing files // that are in use and will be removed on OS reboot. The call to remove the // directory on OS reboot is done after the calls to remove the files so the @@ -3743,6 +3991,7 @@ int NS_main(int argc, NS_tchar** argv) { "directory: " LOG_S, DELETE_DIR)); } +# endif } #endif /* XP_WIN */ @@ -4384,7 +4633,13 @@ int DoUpdate() { action = new AddIfNotFile(); } else if (NS_tstrcmp(token, NS_T("patch-if")) == 0) { // Patch if exists action = new PatchIfFile(); - } else { + } +#ifndef XP_WIN + else if (NS_tstrcmp(token, NS_T("addsymlink")) == 0) { + action = new AddSymlink(); + } +#endif + else { LOG(("DoUpdate: unknown token: " LOG_S, token)); free(buf); return PARSE_ERROR; diff --git a/toolkit/xre/MacLaunchHelper.h b/toolkit/xre/MacLaunchHelper.h index f8dc75ee4d08..ce816acd83e2 100644 --- a/toolkit/xre/MacLaunchHelper.h +++ b/toolkit/xre/MacLaunchHelper.h @@ -17,7 +17,9 @@ extern "C" { * pid of the terminated process to confirm that it executed successfully. */ void LaunchChildMac(int aArgc, char** aArgv, pid_t* aPid = 0); +#ifndef TOR_BROWSER_UPDATE bool LaunchElevatedUpdate(int aArgc, char** aArgv, pid_t* aPid = 0); +#endif } #endif diff --git a/toolkit/xre/MacLaunchHelper.mm b/toolkit/xre/MacLaunchHelper.mm index ec570ffab124..da2917c2a99e 100644 --- a/toolkit/xre/MacLaunchHelper.mm +++ b/toolkit/xre/MacLaunchHelper.mm @@ -40,6 +40,7 @@ void LaunchChildMac(int aArgc, char** aArgv, pid_t* aPid) { } } +#ifndef TOR_BROWSER_UPDATE BOOL InstallPrivilegedHelper() { AuthorizationRef authRef = NULL; OSStatus status = AuthorizationCreate( @@ -116,3 +117,4 @@ bool LaunchElevatedUpdate(int aArgc, char** aArgv, pid_t* aPid) { } return didSucceed; } +#endif diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index bda56973ad71..38932d126767 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -2754,6 +2754,11 @@ static bool CheckCompatibility(nsIFile* aProfileDir, const nsCString& aVersion, gLastAppBuildID.Assign(gAppData->buildID); nsAutoCString buf; + + nsAutoCString tbVersion(TOR_BROWSER_VERSION_QUOTED); + rv = parser.GetString("Compatibility", "LastTorBrowserVersion", buf); + if (NS_FAILED(rv) || !tbVersion.Equals(buf)) return false; + rv = parser.GetString("Compatibility", "LastOSABI", buf); if (NS_FAILED(rv) || !aOSABI.Equals(buf)) return false; @@ -2839,6 +2844,12 @@ static void WriteVersion(nsIFile* aProfileDir, const nsCString& aVersion, PR_Write(fd, kHeader, sizeof(kHeader) - 1); PR_Write(fd, aVersion.get(), aVersion.Length()); + nsAutoCString tbVersion(TOR_BROWSER_VERSION_QUOTED); + static const char kTorBrowserVersionHeader[] = + NS_LINEBREAK "LastTorBrowserVersion="; + PR_Write(fd, kTorBrowserVersionHeader, sizeof(kTorBrowserVersionHeader) - 1); + PR_Write(fd, tbVersion.get(), tbVersion.Length()); + static const char kOSABIHeader[] = NS_LINEBREAK "LastOSABI="; PR_Write(fd, kOSABIHeader, sizeof(kOSABIHeader) - 1); PR_Write(fd, aOSABI.get(), aOSABI.Length()); @@ -4285,8 +4296,17 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) { if (CheckArg("test-process-updates")) { SaveToEnv("MOZ_TEST_PROCESS_UPDATES=1"); } +# ifdef TOR_BROWSER_UPDATE + nsAutoCString compatVersion(TOR_BROWSER_VERSION_QUOTED); +# endif ProcessUpdates(mDirProvider.GetGREDir(), exeDir, updRoot, gRestartArgc, - gRestartArgv, mAppData->version); + gRestartArgv, +# ifdef TOR_BROWSER_UPDATE + compatVersion.get() +# else + mAppData->version +# endif + ); if (EnvHasValue("MOZ_TEST_PROCESS_UPDATES")) { SaveToEnv("MOZ_TEST_PROCESS_UPDATES="); *aExitFlag = true; diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp index 3694ec6f364c..20ad18b71dde 100644 --- a/toolkit/xre/nsUpdateDriver.cpp +++ b/toolkit/xre/nsUpdateDriver.cpp @@ -159,6 +159,13 @@ static nsresult GetInstallDirPath(nsIFile* appDir, nsACString& installDirPath) { return NS_OK; } +#ifdef DEBUG +static void dump_argv(const char* aPrefix, char** argv, int argc) { + printf("%s - %d args\n", aPrefix, argc); + for (int i = 0; i < argc; ++i) printf(" %d: %s\n", i, argv[i]); +} +#endif + static bool GetFile(nsIFile* dir, const nsACString& name, nsCOMPtr<nsIFile>& result) { nsresult rv; @@ -220,6 +227,34 @@ typedef enum { eAppliedService, } UpdateStatus; +#ifdef DEBUG +static const char* UpdateStatusToString(UpdateStatus aStatus) { + const char* rv = "unknown"; + switch (aStatus) { + case eNoUpdateAction: + rv = "NoUpdateAction"; + break; + case ePendingUpdate: + rv = "PendingUpdate"; + break; + case ePendingService: + rv = "PendingService"; + break; + case ePendingElevate: + rv = "PendingElevate"; + break; + case eAppliedUpdate: + rv = "AppliedUpdate"; + break; + case eAppliedService: + rv = "AppliedService"; + break; + } + + return rv; +} +#endif + /** * Returns a value indicating what needs to be done in order to handle an * update. @@ -292,9 +327,39 @@ static bool IsOlderVersion(nsIFile* versionFile, const char* appVersion) { return false; } +#ifdef DEBUG + printf("IsOlderVersion checking appVersion %s against updateVersion %s\n", + appVersion, buf); +#endif + return mozilla::Version(appVersion) > buf; } +#ifndef TOR_BROWSER_DATA_OUTSIDE_APP_DIR +# if defined(TOR_BROWSER_UPDATE) && defined(XP_MACOSX) +static nsresult GetUpdateDirFromAppDir(nsIFile* aAppDir, nsIFile** aResult) { + // On Mac OSX, we stage the update to an Updated.app directory that is + // directly below the main Tor Browser.app directory (two levels up from + // the appDir). + NS_ENSURE_ARG_POINTER(aAppDir); + NS_ENSURE_ARG_POINTER(aResult); + nsCOMPtr<nsIFile> parentDir1, parentDir2; + nsresult rv = aAppDir->GetParent(getter_AddRefs(parentDir1)); + NS_ENSURE_SUCCESS(rv, rv); + rv = parentDir1->GetParent(getter_AddRefs(parentDir2)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> updatedDir; + if (!GetFile(parentDir2, "Updated.app"_ns, updatedDir)) { + return NS_ERROR_FAILURE; + } + + updatedDir.forget(aResult); + return NS_OK; +} +# endif +#endif + /** * Applies, switches, or stages an update. * @@ -442,7 +507,12 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, } else { // Get the directory where the update is staged or will be staged. #if defined(XP_MACOSX) +# if defined(TOR_BROWSER_UPDATE) && !defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) + rv = GetUpdateDirFromAppDir(appDir, getter_AddRefs(updatedDir)); + if (NS_FAILED(rv)) { +# else if (!GetFile(updateDir, "Updated.app"_ns, updatedDir)) { +# endif #else if (!GetFile(appDir, "updated"_ns, updatedDir)) { #endif @@ -537,6 +607,9 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, } LOG(("spawning updater process [%s]\n", updaterPath.get())); +#ifdef DEBUG + dump_argv("ApplyUpdate updater", argv, argc); +#endif #if defined(XP_UNIX) && !defined(XP_MACOSX) // We use execv to spawn the updater process on all UNIX systems except Mac @@ -574,6 +647,10 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, } #elif defined(XP_MACOSX) UpdateDriverSetupMacCommandLine(argc, argv, restart); +# ifdef DEBUG +dump_argv("ApplyUpdate after SetupMacCommandLine", argv, argc); +# endif +# ifndef TOR_BROWSER_UPDATE // We need to detect whether elevation is required for this update. This can // occur when an admin user installs the application, but another admin // user attempts to update (see bug 394984). @@ -586,6 +663,7 @@ if (restart && !IsRecursivelyWritable(installDirPath.get())) { } exit(0); } +# endif if (isStaged) { // Launch the updater to replace the installation with the staged updated. @@ -656,9 +734,27 @@ static bool ProcessHasTerminated(ProcessType pt) { nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir, int argc, char** argv, const char* appVersion, bool restart, ProcessType* pid) { +#if defined(XP_WIN) && defined(TOR_BROWSER_UPDATE) + // Try to remove the "tobedeleted" directory which, if present, contains + // files that could not be removed during a previous update (e.g., DLLs + // that were in use and therefore locked by Windows). + nsCOMPtr<nsIFile> deleteDir; + nsresult winrv = appDir->Clone(getter_AddRefs(deleteDir)); + if (NS_SUCCEEDED(winrv)) { + winrv = deleteDir->AppendNative("tobedeleted"_ns); + if (NS_SUCCEEDED(winrv)) { + winrv = deleteDir->Remove(true); + } + } +#endif + nsresult rv; nsCOMPtr<nsIFile> updatesDir; +#ifdef DEBUG + printf("ProcessUpdates updateRootDir: %s appVersion: %s\n", + updRootDir->HumanReadablePath().get(), appVersion); +#endif rv = updRootDir->Clone(getter_AddRefs(updatesDir)); NS_ENSURE_SUCCESS(rv, rv); rv = updatesDir->AppendNative("updates"_ns); @@ -678,6 +774,12 @@ nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir, nsCOMPtr<nsIFile> statusFile; UpdateStatus status = GetUpdateStatus(updatesDir, statusFile); +#ifdef DEBUG + printf("ProcessUpdates status: %s (%d)\n", UpdateStatusToString(status), + status); + printf("ProcessUpdates updatesDir: %s\n", + updatesDir->HumanReadablePath().get()); +#endif switch (status) { case ePendingUpdate: case ePendingService: { @@ -741,13 +843,16 @@ nsUpdateProcessor::ProcessUpdate() { NS_ENSURE_SUCCESS(rv, rv); } + nsAutoCString appVersion; +#ifdef TOR_BROWSER_UPDATE + appVersion = TOR_BROWSER_VERSION_QUOTED; +#else nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1", &rv); NS_ENSURE_SUCCESS(rv, rv); - - nsAutoCString appVersion; rv = appInfo->GetVersion(appVersion); NS_ENSURE_SUCCESS(rv, rv); +#endif // Copy the parameters to the StagedUpdateInfo structure shared with the // watcher thread. diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp index b2ea009553ad..2ec6929b01a5 100644 --- a/toolkit/xre/nsXREDirProvider.cpp +++ b/toolkit/xre/nsXREDirProvider.cpp @@ -1256,6 +1256,41 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult, } #endif nsCOMPtr<nsIFile> updRoot; +#if defined(TOR_BROWSER_UPDATE) + // For Tor Browser, we store update history, etc. within the UpdateInfo + // directory under the user data directory. + nsresult rv = GetTorBrowserUserDataDir(getter_AddRefs(updRoot)); + NS_ENSURE_SUCCESS(rv, rv); + rv = updRoot->AppendNative("UpdateInfo"_ns); + NS_ENSURE_SUCCESS(rv, rv); +# if defined(XP_MACOSX) && defined(TOR_BROWSER_DATA_OUTSIDE_APP_DIR) + // Since the TorBrowser-Data directory may be shared among different + // installations of the application, embed the app path in the update dir + // so that the update history is partitioned. This is much less likely to + // be an issue on Linux or Windows because the Tor Browser packages for + // those platforms include a "container" folder that provides partitioning + // by default, and we do not support use of a shared, OS-recommended area + // for user data on those platforms. + nsCOMPtr<nsIFile> appFile; + bool per = false; + rv = GetFile(XRE_EXECUTABLE_FILE, &per, getter_AddRefs(appFile)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIFile> appRootDirFile; + nsAutoString appDirPath; + if (NS_FAILED(appFile->GetParent(getter_AddRefs(appRootDirFile))) || + NS_FAILED(appRootDirFile->GetPath(appDirPath))) { + return NS_ERROR_FAILURE; + } + + int32_t dotIndex = appDirPath.RFind(".app"); + if (dotIndex == kNotFound) { + dotIndex = appDirPath.Length(); + } + appDirPath = Substring(appDirPath, 1, dotIndex - 1); + rv = updRoot->AppendRelativePath(appDirPath); + NS_ENSURE_SUCCESS(rv, rv); +# endif +#else // ! TOR_BROWSER_UPDATE nsCOMPtr<nsIFile> appFile; bool per = false; nsresult rv = GetFile(XRE_EXECUTABLE_FILE, &per, getter_AddRefs(appFile)); @@ -1263,7 +1298,7 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult, rv = appFile->GetParent(getter_AddRefs(updRoot)); NS_ENSURE_SUCCESS(rv, rv); -#ifdef XP_MACOSX +# ifdef XP_MACOSX nsCOMPtr<nsIFile> appRootDirFile; nsCOMPtr<nsIFile> localDir; nsAutoString appDirPath; @@ -1297,7 +1332,7 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult, localDir.forget(aResult); return NS_OK; -#elif XP_WIN +# elif XP_WIN nsAutoString installPath; rv = updRoot->GetPath(installPath); NS_ENSURE_SUCCESS(rv, rv); @@ -1326,7 +1361,8 @@ nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult, nsAutoString updatePathStr; updatePathStr.Assign(updatePath.get()); updRoot->InitWithPath(updatePathStr); -#endif // XP_WIN +# endif // XP_WIN +#endif // ! TOR_BROWSER_UPDATE updRoot.forget(aResult); return NS_OK; } diff --git a/tools/update-packaging/common.sh b/tools/update-packaging/common.sh index 4b994f30169c..26eabbf31379 100755 --- a/tools/update-packaging/common.sh +++ b/tools/update-packaging/common.sh @@ -8,6 +8,10 @@ # Author: Darin Fisher # +# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms, +# we should remove all lines in this file that contain: +# TorBrowser/Data + # ----------------------------------------------------------------------------- QUIET=0 @@ -76,17 +80,8 @@ make_add_instruction() { forced= fi - is_extension=$(echo "$f" | grep -c 'distribution/extensions/.*/') - if [ $is_extension = "1" ]; then - # Use the subdirectory of the extensions folder as the file to test - # before performing this add instruction. - testdir=$(echo "$f" | sed 's/\(.*distribution\/extensions\/[^\/]*\)\/.*/\1/') - verbose_notice " add-if \"$testdir\" \"$f\"" - echo "add-if \"$testdir\" \"$f\"" >> "$filev3" - else - verbose_notice " add \"$f\"$forced" - echo "add \"$f\"" >> "$filev3" - fi + verbose_notice " add \"$f\"$forced" + echo "add \"$f\"" >> "$filev3" } check_for_add_if_not_update() { @@ -109,21 +104,21 @@ make_add_if_not_instruction() { echo "add-if-not \"$f\" \"$f\"" >> "$filev3" } +make_addsymlink_instruction() { + link="$1" + target="$2" + filev3="$3" + + verbose_notice " addsymlink: $link -> $target" + echo "addsymlink \"$link\" \"$target\"" >> "$filev3" +} + make_patch_instruction() { f="$1" filev3="$2" - is_extension=$(echo "$f" | grep -c 'distribution/extensions/.*/') - if [ $is_extension = "1" ]; then - # Use the subdirectory of the extensions folder as the file to test - # before performing this add instruction. - testdir=$(echo "$f" | sed 's/\(.*distribution\/extensions\/[^\/]*\)\/.*/\1/') - verbose_notice " patch-if \"$testdir\" \"$f.patch\" \"$f\"" - echo "patch-if \"$testdir\" \"$f.patch\" \"$f\"" >> "$filev3" - else - verbose_notice " patch \"$f.patch\" \"$f\"" - echo "patch \"$f.patch\" \"$f\"" >> "$filev3" - fi + verbose_notice " patch \"$f.patch\" \"$f\"" + echo "patch \"$f.patch\" \"$f\"" >> "$filev3" } append_remove_instructions() { @@ -168,6 +163,10 @@ append_remove_instructions() { # List all files in the current directory, stripping leading "./" # Pass a variable name and it will be filled as an array. +# To support Tor Browser updates, skip the following files: +# TorBrowser/Data/Browser/profiles.ini +# TorBrowser/Data/Browser/profile.default/bookmarks.html +# TorBrowser/Data/Tor/torrc list_files() { count=0 temp_filelist=$(mktemp) @@ -178,6 +177,11 @@ list_files() { | sed 's/\.\/\(.*\)/\1/' \ | sort -r > "${temp_filelist}" while read file; do + if [ "$file" = "TorBrowser/Data/Browser/profiles.ini" -o \ + "$file" = "TorBrowser/Data/Browser/profile.default/bookmarks.html" -o \ + "$file" = "TorBrowser/Data/Tor/torrc" ]; then + continue; + fi eval "${1}[$count]=\"$file\"" (( count++ )) done < "${temp_filelist}" @@ -199,3 +203,19 @@ list_dirs() { done < "${temp_dirlist}" rm "${temp_dirlist}" } + +# List all symbolic links in the current directory, stripping leading "./" +list_symlinks() { + count=0 + + find . -type l \ + | sed 's/\.\/\(.*\)/\1/' \ + | sort -r > "temp-symlinklist" + while read symlink; do + target=$(readlink "$symlink") + eval "${1}[$count]=\"$symlink\"" + eval "${2}[$count]=\"$target\"" + (( count++ )) + done < "temp-symlinklist" + rm "temp-symlinklist" +} diff --git a/tools/update-packaging/make_full_update.sh b/tools/update-packaging/make_full_update.sh index db2c5898efdc..cc75a21dd51a 100755 --- a/tools/update-packaging/make_full_update.sh +++ b/tools/update-packaging/make_full_update.sh @@ -71,6 +71,20 @@ if [ ! -f "precomplete" ]; then fi list_files files +list_symlinks symlinks symlink_targets + +# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms, +# we should remove the following lines (which remove entire directories +# which, if present, contain old, unpacked copies of HTTPS Everywhere): +# Make sure we delete the pre 5.1.0 HTTPS Everywhere as well in case it +# exists. The extension ID got changed with the version bump to 5.1.0. +ext_path='TorBrowser/Data/Browser/profile.default/extensions' +if [ -d "$ext_dir" ]; then + directories_to_remove="$ext_path/https-everywhere(a)eff.org $ext_path/https-everywhere-eff(a)eff.org" +else + directories_to_remove="" +fi +# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal popd @@ -81,6 +95,21 @@ notice "Adding type instruction to update manifests" notice " type complete" echo "type \"complete\"" >> "$updatemanifestv3" +# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms, +# we should remove the following lines: +# If removal of any old, existing directories is desired, emit the appropriate +# rmrfdir commands. +notice "" +notice "Adding directory removal instructions to update manifests" +for dir_to_remove in $directories_to_remove; do + # rmrfdir requires a trailing slash; if slash is missing, add one. + if ! [[ "$dir_to_remove" =~ /$ ]]; then + dir_to_remove="${dir_to_remove}/" + fi + echo "rmrfdir \"$dir_to_remove\"" >> "$updatemanifestv3" +done +# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal + notice "" notice "Adding file add instructions to update manifests" num_files=${#files[*]} @@ -102,6 +131,15 @@ for ((i=0; $i<$num_files; i=$i+1)); do targetfiles="$targetfiles \"$f\"" done +notice "" +notice "Adding symlink add instructions to update manifests" +num_symlinks=${#symlinks[*]} +for ((i=0; $i<$num_symlinks; i=$i+1)); do + link="${symlinks[$i]}" + target="${symlink_targets[$i]}" + make_addsymlink_instruction "$link" "$target" "$updatemanifestv3" +done + # Append remove instructions for any dead files. notice "" notice "Adding file and directory remove instructions from file 'removed-files'" diff --git a/tools/update-packaging/make_incremental_update.sh b/tools/update-packaging/make_incremental_update.sh index 24d68616731a..2dd1e3077823 100755 --- a/tools/update-packaging/make_incremental_update.sh +++ b/tools/update-packaging/make_incremental_update.sh @@ -68,6 +68,21 @@ check_for_forced_update() { ## "true" *giggle* return 0; fi + +# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms, +# we should remove the following lines: + # If the file in the skip list ends with /*, do a prefix match. + # This allows TorBrowser/Data/Browser/profile.default/extensions/https-everywhere-eff(a)eff.org/* + # to be used to force all HTTPS Everywhere files to be updated. + f_suffix=${f##*/} + if [[ $f_suffix = "*" ]]; then + f_prefix="${f%\/\*}"; + if [[ $forced_file_chk == $f_prefix* ]]; then + ## 0 means "true" + return 0; + fi + fi +# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal done ## 'false'... because this is bash. Oh yay! return 1; @@ -78,7 +93,11 @@ if [ $# = 0 ]; then exit 1 fi -requested_forced_updates='Contents/MacOS/firefox' +# Firefox uses requested_forced_updates='Contents/MacOS/firefox' due to +# 770996 but in Tor Browser we do not need that fix. +requested_forced_updates="" +directories_to_remove="" +extra_files_to_remove="" while getopts "hqf:" flag do @@ -114,6 +133,83 @@ workdir="$(mktemp -d)" updatemanifestv3="$workdir/updatev3.manifest" archivefiles="updatev3.manifest" +# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms, +# we should remove the following lines: +# If the NoScript or HTTPS Everywhere extensions have changed between +# releases, add them to the "force updates" list. +ext_path='TorBrowser/Data/Browser/profile.default/extensions' +if [ -d "$newdir/$ext_path" ]; then + https_everywhere_dir='https-everywhere-eff(a)eff.org' + https_everywhere_xpi='https-everywhere-eff(a)eff.org.xpi' + noscript='{73a6fe31-595d-460b-a920-fcc0f8843232}.xpi' + + # NoScript is a packed extension, so we simply compare the old and the new + # .xpi files. + noscript_path="$ext_path/$noscript" + diff -a "$olddir/$noscript_path" "$newdir/$noscript_path" > /dev/null + rc=$? + if [ $rc -gt 1 ]; then + notice "Unexpected exit $rc from $noscript_path diff command" + exit 2 + elif [ $rc -eq 1 ]; then + requested_forced_updates="$requested_forced_updates $noscript_path" + fi + + # As of HTTPS Everywhere 5.1.0, the extension ID gained "-eff". + # As of HTTPS Everywhere 5.2.2, the extension is packed (i.e., it remains + # an .xpi after it is installed in the browser profile). + force_https_update=0 + remove_unpacked_https_e_dirs=0 + unpacked_https_e_install_rdf="$ext_path/$https_everywhere_dir/install.rdf" + packed_https_e_path="$ext_path/$https_everywhere_xpi" + if [ -d "$newdir/$ext_path/$https_everywhere_dir" ]; then + # The new HTTPS-E extension is unpacked, and presumably the old one is + # too. We need to determine if any of the unpacked files have changed. + # Since that is messy, we simply compare the old install.rdf file to the + # new one. + diff "$olddir/$unpacked_https_e_install_rdf" \ + "$newdir/$unpacked_https_e_install_rdf" > /dev/null + rc=$? + if [ $rc -gt 1 -a -e "$olddir/$unpacked_https_e_install_rdf" ]; then + notice "Unexpected exit $rc from $unpacked_https_e_install_rdf diff command" + exit 2 + elif [ $rc -ge 1 ]; then + force_https_update=1 + remove_unpacked_https_e_dirs=1 + # In case we still ship an unpacked HTTPS-E extension but the user has + # updated to a packed one, arrange for the packed one to be removed. + extra_files_to_remove="$extra_files_to_remove $packed_https_e_path" + fi + elif [ -d "$olddir/$ext_path/$https_everywhere_dir" ]; then + # The old HTTPS-E extension is unpacked but the new one is packed. + force_https_update=1 + remove_unpacked_https_e_dirs=1 + else + # Both the old and new HTTPS-E extensions are packed. In this case we can + # simply compare the .xpi files to determine if the extension has changed. + diff -a "$olddir/$packed_https_e_path" "$newdir/$packed_https_e_path" > /dev/null + rc=$? + if [ $rc -gt 1 ]; then + notice "Unexpected exit $rc from $packed_https_e_path diff command" + exit 2 + elif [ $rc -eq 1 ]; then + force_https_update=1 + fi + fi + + if [ $force_https_update -ne 0 ]; then + requested_forced_updates="$requested_forced_updates $ext_path/$https_everywhere_dir/* $packed_https_e_path" + if [ "$remove_unpacked_https_e_dirs" -ne 0 ]; then + # The old version was unpacked, so remove the entire directory to ensure + # that the replace is "clean." Also, make sure we delete the pre 5.1.0 + # HTTPS Everywhere as well in case it exists (the extension ID got + # changed with the version bump to 5.1.0). + directories_to_remove="$directories_to_remove $ext_path/https-everywhere(a)eff.org $ext_path/$https_everywhere_dir" + fi + fi +fi +# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal + mkdir -p "$workdir" # Generate a list of all files in the target directory. @@ -124,6 +220,7 @@ fi list_files oldfiles list_dirs olddirs +list_symlinks oldsymlinks oldsymlink_targets popd @@ -141,6 +238,7 @@ fi list_dirs newdirs list_files newfiles +list_symlinks newsymlinks newsymlink_targets popd @@ -151,6 +249,22 @@ notice "Adding type instruction to update manifests" notice " type partial" echo "type \"partial\"" >> $updatemanifestv3 +# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms, +# we should remove the following lines: +# If removal of any old, existing directories is desired, emit the appropriate +# rmrfdir commands. +notice "" +notice "Adding directory removal instructions to update manifests" +for dir_to_remove in $directories_to_remove; do + # rmrfdir requires a trailing slash, so add one if missing. + if ! [[ "$dir_to_remove" =~ /$ ]]; then + dir_to_remove="${dir_to_remove}/" + fi + echo "rmrfdir \"$dir_to_remove\"" >> "$updatemanifestv3" +done +# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal + + notice "" notice "Adding file patch and add instructions to update manifests" @@ -242,6 +356,23 @@ for ((i=0; $i<$num_oldfiles; i=$i+1)); do fi done +# Remove and re-add symlinks +notice "" +notice "Adding symlink remove/add instructions to update manifests" +num_oldsymlinks=${#oldsymlinks[*]} +for ((i=0; $i<$num_oldsymlinks; i=$i+1)); do + link="${oldsymlinks[$i]}" + verbose_notice " remove: $link" + echo "remove \"$link\"" >> "$updatemanifestv3" +done + +num_newsymlinks=${#newsymlinks[*]} +for ((i=0; $i<$num_newsymlinks; i=$i+1)); do + link="${newsymlinks[$i]}" + target="${newsymlink_targets[$i]}" + make_addsymlink_instruction "$link" "$target" "$updatemanifestv3" +done + # Newly added files notice "" notice "Adding file add instructions to update manifests" @@ -286,6 +417,14 @@ notice "" notice "Adding file and directory remove instructions from file 'removed-files'" append_remove_instructions "$newdir" "$updatemanifestv3" +# TODO When TOR_BROWSER_DATA_OUTSIDE_APP_DIR is used on all platforms, +# we should remove the following lines: +for f in $extra_files_to_remove; do + notice " remove \"$f\"" + echo "remove \"$f\"" >> "$updatemanifestv3" +done +# END TOR_BROWSER_DATA_OUTSIDE_APP_DIR removal + notice "" notice "Adding directory remove instructions for directories that no longer exist" num_olddirs=${#olddirs[*]}
1 0
0 0
  • ← Newer
  • 1
  • ...
  • 1447
  • 1448
  • 1449
  • 1450
  • 1451
  • 1452
  • 1453
  • ...
  • 2064
  • Older →

HyperKitty Powered by HyperKitty version 1.3.12.