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 };