tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

FirefoxBridgeExtensionUtils.sys.mjs (8553B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
      6 
      7 const lazy = {};
      8 ChromeUtils.defineESModuleGetters(lazy, {
      9  ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
     10 });
     11 
     12 /**
     13 * Default implementation of the helper class to assist in deleting the firefox protocols.
     14 * See maybeDeleteBridgeProtocolRegistryEntries for more info.
     15 */
     16 class DeleteBridgeProtocolRegistryEntryHelperImplementation {
     17  getApplicationPath() {
     18    return Services.dirsvc.get("XREExeF", Ci.nsIFile).path;
     19  }
     20 
     21  openRegistryRoot() {
     22    const wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
     23      Ci.nsIWindowsRegKey
     24    );
     25 
     26    wrk.open(wrk.ROOT_KEY_CURRENT_USER, "Software\\Classes", wrk.ACCESS_ALL);
     27 
     28    return wrk;
     29  }
     30 
     31  deleteChildren(start) {
     32    // Recursively delete all of the children of the children
     33    // Go through the list in reverse order, so that shrinking
     34    // the list doesn't rearrange things while iterating
     35    for (let i = start.childCount; i > 0; i--) {
     36      const childName = start.getChildName(i - 1);
     37      const child = start.openChild(childName, start.ACCESS_ALL);
     38      this.deleteChildren(child);
     39      child.close();
     40 
     41      start.removeChild(childName);
     42    }
     43  }
     44 
     45  deleteRegistryTree(root, toDeletePath) {
     46    var start = root.openChild(toDeletePath, root.ACCESS_ALL);
     47    this.deleteChildren(start);
     48    start.close();
     49 
     50    root.removeChild(toDeletePath);
     51  }
     52 }
     53 
     54 export const FirefoxBridgeExtensionUtils = {
     55  /**
     56   * In Firefox 122, we enabled the firefox and firefox-private protocols.
     57   * We switched over to using firefox-bridge and firefox-private-bridge,
     58   *
     59   * In Firefox 126, we deleted the above firefox-bridge and
     60   * firefox-private-bridge protocols in favor of using native
     61   * messaging so we are only keeping the deletion code.
     62   *
     63   * but we want to clean up the use of the other protocols.
     64   *
     65   * deleteBridgeProtocolRegistryEntryHelper handles everything outside of the logic needed for
     66   * this method so that the logic in maybeDeleteBridgeProtocolRegistryEntries can be unit tested
     67   *
     68   * We only delete the entries for the firefox and firefox-private protocols if
     69   * they were set up to use this install and in the format that Firefox installed
     70   * them with. If the entries are changed in any way, it is assumed that the user
     71   * mucked with them manually and knows what they are doing.
     72   */
     73 
     74  PUBLIC_PROTOCOL: "firefox-bridge",
     75  PRIVATE_PROTOCOL: "firefox-private-bridge",
     76  OLD_PUBLIC_PROTOCOL: "firefox",
     77  OLD_PRIVATE_PROTOCOL: "firefox-private",
     78 
     79  maybeDeleteBridgeProtocolRegistryEntries(
     80    publicProtocol = this.PUBLIC_PROTOCOL,
     81    privateProtocol = this.PRIVATE_PROTOCOL,
     82    deleteBridgeProtocolRegistryEntryHelper = new DeleteBridgeProtocolRegistryEntryHelperImplementation()
     83  ) {
     84    try {
     85      var wrk = deleteBridgeProtocolRegistryEntryHelper.openRegistryRoot();
     86      const path = deleteBridgeProtocolRegistryEntryHelper.getApplicationPath();
     87 
     88      const maybeDeleteRegistryKey = (protocol, protocolCommand) => {
     89        const openCommandPath = protocol + "\\shell\\open\\command";
     90        if (wrk.hasChild(openCommandPath)) {
     91          let deleteProtocolEntry = false;
     92 
     93          try {
     94            var openCommandKey = wrk.openChild(
     95              openCommandPath,
     96              wrk.ACCESS_READ
     97            );
     98            if (openCommandKey.valueCount == 1) {
     99              const defaultKeyName = "";
    100              if (openCommandKey.getValueName(0) == defaultKeyName) {
    101                if (
    102                  openCommandKey.getValueType(defaultKeyName) ==
    103                  Ci.nsIWindowsRegKey.TYPE_STRING
    104                ) {
    105                  const val = openCommandKey.readStringValue(defaultKeyName);
    106                  if (val == protocolCommand) {
    107                    deleteProtocolEntry = true;
    108                  }
    109                }
    110              }
    111            }
    112          } finally {
    113            openCommandKey.close();
    114          }
    115 
    116          if (deleteProtocolEntry) {
    117            deleteBridgeProtocolRegistryEntryHelper.deleteRegistryTree(
    118              wrk,
    119              protocol
    120            );
    121          }
    122        }
    123      };
    124 
    125      maybeDeleteRegistryKey(publicProtocol, `\"${path}\" -osint -url \"%1\"`);
    126      maybeDeleteRegistryKey(
    127        privateProtocol,
    128        `\"${path}\" -osint -private-window \"%1\"`
    129      );
    130    } catch (err) {
    131      console.error(err);
    132    } finally {
    133      wrk.close();
    134    }
    135  },
    136 
    137  getNativeMessagingHostId() {
    138    let nativeMessagingHostId = "org.mozilla.firefox_bridge_nmh";
    139    if (AppConstants.NIGHTLY_BUILD) {
    140      nativeMessagingHostId += "_nightly";
    141    } else if (AppConstants.MOZ_DEV_EDITION) {
    142      nativeMessagingHostId += "_dev";
    143    } else if (AppConstants.IS_ESR) {
    144      nativeMessagingHostId += "_esr";
    145    }
    146    return nativeMessagingHostId;
    147  },
    148 
    149  getExtensionOrigins() {
    150    return Services.prefs
    151      .getStringPref("browser.firefoxbridge.extensionOrigins", "")
    152      .split(",");
    153  },
    154 
    155  async maybeWriteManifestFiles(
    156    nmhManifestFolder,
    157    nativeMessagingHostId,
    158    dualBrowserExtensionOrigins
    159  ) {
    160    try {
    161      let binFile = Services.dirsvc.get("XREExeF", Ci.nsIFile).parent;
    162      if (AppConstants.platform == "win") {
    163        binFile.append("nmhproxy.exe");
    164      } else if (AppConstants.platform == "macosx") {
    165        binFile.append("nmhproxy");
    166      } else {
    167        throw new Error("Unsupported platform");
    168      }
    169 
    170      let jsonContent = {
    171        name: nativeMessagingHostId,
    172        description: "Firefox Native Messaging Host",
    173        path: binFile.path,
    174        type: "stdio",
    175        allowed_origins: dualBrowserExtensionOrigins,
    176      };
    177      let nmhManifestFile = await IOUtils.getFile(
    178        nmhManifestFolder,
    179        `${nativeMessagingHostId}.json`
    180      );
    181 
    182      // This throws an error if the JSON file doesn't exist
    183      // or if it's corrupt.
    184      let correctFileExists = true;
    185      try {
    186        correctFileExists = lazy.ObjectUtils.deepEqual(
    187          await IOUtils.readJSON(nmhManifestFile.path),
    188          jsonContent
    189        );
    190      } catch (e) {
    191        correctFileExists = false;
    192      }
    193      if (!correctFileExists) {
    194        await IOUtils.writeJSON(nmhManifestFile.path, jsonContent);
    195      }
    196    } catch (e) {
    197      console.error(e);
    198    }
    199  },
    200 
    201  async ensureRegistered() {
    202    let nmhManifestFolder = null;
    203    if (AppConstants.platform == "win") {
    204      // We don't have permission to write to the application install directory
    205      // so instead write to %AppData%\Mozilla\Firefox.
    206      nmhManifestFolder = PathUtils.join(
    207        Services.dirsvc.get("AppData", Ci.nsIFile).path,
    208        "Mozilla",
    209        "Firefox"
    210      );
    211    } else if (AppConstants.platform == "macosx") {
    212      nmhManifestFolder =
    213        "~/Library/Application Support/Google/Chrome/NativeMessagingHosts/";
    214    } else {
    215      throw new Error("Unsupported platform");
    216    }
    217    await this.maybeWriteManifestFiles(
    218      nmhManifestFolder,
    219      this.getNativeMessagingHostId(),
    220      this.getExtensionOrigins()
    221    );
    222    if (AppConstants.platform == "win") {
    223      this.maybeWriteNativeMessagingRegKeys(
    224        "Software\\Google\\Chrome\\NativeMessagingHosts",
    225        nmhManifestFolder,
    226        this.getNativeMessagingHostId()
    227      );
    228    }
    229  },
    230 
    231  maybeWriteNativeMessagingRegKeys(
    232    regPath,
    233    nmhManifestFolder,
    234    NATIVE_MESSAGING_HOST_ID
    235  ) {
    236    let wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
    237      Ci.nsIWindowsRegKey
    238    );
    239    try {
    240      let expectedValue = PathUtils.join(
    241        nmhManifestFolder,
    242        `${NATIVE_MESSAGING_HOST_ID}.json`
    243      );
    244      try {
    245        // If the key already exists it will just be opened
    246        wrk.create(
    247          wrk.ROOT_KEY_CURRENT_USER,
    248          regPath + `\\${NATIVE_MESSAGING_HOST_ID}`,
    249          wrk.ACCESS_ALL
    250        );
    251        if (wrk.readStringValue("") == expectedValue) {
    252          return;
    253        }
    254      } catch (e) {
    255        // The key either doesn't have a value or doesn't exist
    256        // In either case we need to write it.
    257      }
    258      wrk.writeStringValue("", expectedValue);
    259    } catch (e) {
    260      // The method fails if we can't access the key
    261      // which means it doesn't exist. That's a normal situation.
    262      // We don't need to do anything here.
    263    } finally {
    264      wrk.close();
    265    }
    266  },
    267 };