Bug 1874684 - Part 31: Correctly reject invalid durations in some RoundDuration calls...
[gecko.git] / browser / modules / FirefoxBridgeExtensionUtils.sys.mjs
blob7b0094205d3a0e1806fda33da2936481cd0e339f
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/. */
5 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
7 const lazy = {};
8 ChromeUtils.defineESModuleGetters(lazy, {
9   ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
10 });
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   }
21   openRegistryRoot() {
22     const wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
23       Ci.nsIWindowsRegKey
24     );
26     wrk.open(wrk.ROOT_KEY_CURRENT_USER, "Software\\Classes", wrk.ACCESS_ALL);
28     return wrk;
29   }
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();
41       start.removeChild(childName);
42     }
43   }
45   deleteRegistryTree(root, toDeletePath) {
46     var start = root.openChild(toDeletePath, root.ACCESS_ALL);
47     this.deleteChildren(start);
48     start.close();
50     root.removeChild(toDeletePath);
51   }
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    * but we want to clean up the use of the other protocols.
60    *
61    * deleteBridgeProtocolRegistryEntryHelper handles everything outside of the logic needed for
62    * this method so that the logic in maybeDeleteBridgeProtocolRegistryEntries can be unit tested
63    *
64    * We only delete the entries for the firefox and firefox-private protocols if
65    * they were set up to use this install and in the format that Firefox installed
66    * them with. If the entries are changed in any way, it is assumed that the user
67    * mucked with them manually and knows what they are doing.
68    */
69   maybeDeleteBridgeProtocolRegistryEntries(
70     deleteBridgeProtocolRegistryEntryHelper = new DeleteBridgeProtocolRegistryEntryHelperImplementation()
71   ) {
72     try {
73       var wrk = deleteBridgeProtocolRegistryEntryHelper.openRegistryRoot();
74       const path = deleteBridgeProtocolRegistryEntryHelper.getApplicationPath();
76       const maybeDeleteRegistryKey = (protocol, protocolCommand) => {
77         const openCommandPath = protocol + "\\shell\\open\\command";
78         if (wrk.hasChild(openCommandPath)) {
79           let deleteProtocolEntry = false;
81           try {
82             var openCommandKey = wrk.openChild(
83               openCommandPath,
84               wrk.ACCESS_READ
85             );
86             if (openCommandKey.valueCount == 1) {
87               const defaultKeyName = "";
88               if (openCommandKey.getValueName(0) == defaultKeyName) {
89                 if (
90                   openCommandKey.getValueType(defaultKeyName) ==
91                   Ci.nsIWindowsRegKey.TYPE_STRING
92                 ) {
93                   const val = openCommandKey.readStringValue(defaultKeyName);
94                   if (val == protocolCommand) {
95                     deleteProtocolEntry = true;
96                   }
97                 }
98               }
99             }
100           } finally {
101             openCommandKey.close();
102           }
104           if (deleteProtocolEntry) {
105             deleteBridgeProtocolRegistryEntryHelper.deleteRegistryTree(
106               wrk,
107               protocol
108             );
109           }
110         }
111       };
113       maybeDeleteRegistryKey("firefox", `\"${path}\" -osint -url \"%1\"`);
114       maybeDeleteRegistryKey(
115         "firefox-private",
116         `\"${path}\" -osint -private-window \"%1\"`
117       );
118     } catch (err) {
119       console.error(err);
120     } finally {
121       wrk.close();
122     }
123   },
125   /**
126    * Registers the firefox-bridge and firefox-private-bridge protocols
127    * on the Windows platform.
128    */
129   maybeRegisterFirefoxBridgeProtocols() {
130     const FIREFOX_BRIDGE_HANDLER_NAME = "firefox-bridge";
131     const FIREFOX_PRIVATE_BRIDGE_HANDLER_NAME = "firefox-private-bridge";
132     const path = Services.dirsvc.get("XREExeF", Ci.nsIFile).path;
133     let wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
134       Ci.nsIWindowsRegKey
135     );
136     try {
137       wrk.open(wrk.ROOT_KEY_CLASSES_ROOT, "", wrk.ACCESS_READ);
138       let FxSet = wrk.hasChild(FIREFOX_BRIDGE_HANDLER_NAME);
139       let FxPrivateSet = wrk.hasChild(FIREFOX_PRIVATE_BRIDGE_HANDLER_NAME);
140       wrk.close();
141       if (FxSet && FxPrivateSet) {
142         return;
143       }
144       wrk.open(wrk.ROOT_KEY_CURRENT_USER, "Software\\Classes", wrk.ACCESS_ALL);
145       const maybeUpdateRegistry = (isSetAlready, handler, protocolName) => {
146         if (isSetAlready) {
147           return;
148         }
149         let FxKey = wrk.createChild(handler, wrk.ACCESS_ALL);
150         try {
151           // Write URL protocol key
152           FxKey.writeStringValue("", protocolName);
153           FxKey.writeStringValue("URL Protocol", "");
154           FxKey.close();
155           // Write defaultIcon key
156           FxKey.create(
157             FxKey.ROOT_KEY_CURRENT_USER,
158             "Software\\Classes\\" + handler + "\\DefaultIcon",
159             FxKey.ACCESS_ALL
160           );
161           FxKey.open(
162             FxKey.ROOT_KEY_CURRENT_USER,
163             "Software\\Classes\\" + handler + "\\DefaultIcon",
164             FxKey.ACCESS_ALL
165           );
166           FxKey.writeStringValue("", `\"${path}\",1`);
167           FxKey.close();
168           // Write shell\\open\\command key
169           FxKey.create(
170             FxKey.ROOT_KEY_CURRENT_USER,
171             "Software\\Classes\\" + handler + "\\shell",
172             FxKey.ACCESS_ALL
173           );
174           FxKey.create(
175             FxKey.ROOT_KEY_CURRENT_USER,
176             "Software\\Classes\\" + handler + "\\shell\\open",
177             FxKey.ACCESS_ALL
178           );
179           FxKey.create(
180             FxKey.ROOT_KEY_CURRENT_USER,
181             "Software\\Classes\\" + handler + "\\shell\\open\\command",
182             FxKey.ACCESS_ALL
183           );
184           FxKey.open(
185             FxKey.ROOT_KEY_CURRENT_USER,
186             "Software\\Classes\\" + handler + "\\shell\\open\\command",
187             FxKey.ACCESS_ALL
188           );
189           if (handler == FIREFOX_PRIVATE_BRIDGE_HANDLER_NAME) {
190             FxKey.writeStringValue(
191               "",
192               `\"${path}\" -osint -private-window \"%1\"`
193             );
194           } else {
195             FxKey.writeStringValue("", `\"${path}\" -osint -url \"%1\"`);
196           }
197         } catch (ex) {
198           console.error(ex);
199         } finally {
200           FxKey.close();
201         }
202       };
204       try {
205         maybeUpdateRegistry(
206           FxSet,
207           FIREFOX_BRIDGE_HANDLER_NAME,
208           "URL:Firefox Bridge Protocol"
209         );
210       } catch (ex) {
211         console.error(ex);
212       }
214       try {
215         maybeUpdateRegistry(
216           FxPrivateSet,
217           FIREFOX_PRIVATE_BRIDGE_HANDLER_NAME,
218           "URL:Firefox Private Bridge Protocol"
219         );
220       } catch (ex) {
221         console.error(ex);
222       }
223     } catch (ex) {
224       console.error(ex);
225     } finally {
226       wrk.close();
227     }
228   },
230   getNativeMessagingHostId() {
231     let nativeMessagingHostId = "org.mozilla.firefox_bridge_nmh";
232     if (AppConstants.NIGHTLY_BUILD) {
233       nativeMessagingHostId += "_nightly";
234     } else if (AppConstants.MOZ_DEV_EDITION) {
235       nativeMessagingHostId += "_dev";
236     } else if (AppConstants.IS_ESR) {
237       nativeMessagingHostId += "_esr";
238     }
239     return nativeMessagingHostId;
240   },
242   getExtensionOrigins() {
243     return Services.prefs
244       .getStringPref("browser.firefoxbridge.extensionOrigins", "")
245       .split(",");
246   },
248   async maybeWriteManifestFiles(
249     nmhManifestFolder,
250     nativeMessagingHostId,
251     dualBrowserExtensionOrigins
252   ) {
253     try {
254       let binFile = Services.dirsvc.get("XREExeF", Ci.nsIFile).parent;
255       if (AppConstants.platform == "win") {
256         binFile.append("nmhproxy.exe");
257       } else if (AppConstants.platform == "macosx") {
258         binFile.append("nmhproxy");
259       } else {
260         throw new Error("Unsupported platform");
261       }
263       let jsonContent = {
264         name: nativeMessagingHostId,
265         description: "Firefox Native Messaging Host",
266         path: binFile.path,
267         type: "stdio",
268         allowed_origins: dualBrowserExtensionOrigins,
269       };
270       let nmhManifestFile = await IOUtils.getFile(
271         nmhManifestFolder,
272         `${nativeMessagingHostId}.json`
273       );
275       // This throws an error if the JSON file doesn't exist
276       // or if it's corrupt.
277       let correctFileExists = true;
278       try {
279         correctFileExists = lazy.ObjectUtils.deepEqual(
280           await IOUtils.readJSON(nmhManifestFile.path),
281           jsonContent
282         );
283       } catch (e) {
284         correctFileExists = false;
285       }
286       if (!correctFileExists) {
287         await IOUtils.writeJSON(nmhManifestFile.path, jsonContent);
288       }
289     } catch (e) {
290       console.error(e);
291     }
292   },
294   async ensureRegistered() {
295     let nmhManifestFolder = null;
296     if (AppConstants.platform == "win") {
297       // We don't have permission to write to the application install directory
298       // so instead write to %AppData%\Mozilla\Firefox.
299       nmhManifestFolder = PathUtils.join(
300         Services.dirsvc.get("AppData", Ci.nsIFile).path,
301         "Mozilla",
302         "Firefox"
303       );
304     } else if (AppConstants.platform == "macosx") {
305       nmhManifestFolder =
306         "~/Library/Application Support/Google/Chrome/NativeMessagingHosts/";
307     } else {
308       throw new Error("Unsupported platform");
309     }
310     await this.maybeWriteManifestFiles(
311       nmhManifestFolder,
312       this.getNativeMessagingHostId(),
313       this.getExtensionOrigins()
314     );
315     if (AppConstants.platform == "win") {
316       this.maybeWriteNativeMessagingRegKeys(
317         "Software\\Google\\Chrome\\NativeMessagingHosts",
318         nmhManifestFolder,
319         this.getNativeMessagingHostId()
320       );
321     }
322   },
324   maybeWriteNativeMessagingRegKeys(
325     regPath,
326     nmhManifestFolder,
327     NATIVE_MESSAGING_HOST_ID
328   ) {
329     let wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
330       Ci.nsIWindowsRegKey
331     );
332     try {
333       let expectedValue = PathUtils.join(
334         nmhManifestFolder,
335         `${NATIVE_MESSAGING_HOST_ID}.json`
336       );
337       try {
338         // If the key already exists it will just be opened
339         wrk.create(
340           wrk.ROOT_KEY_CURRENT_USER,
341           regPath + `\\${NATIVE_MESSAGING_HOST_ID}`,
342           wrk.ACCESS_ALL
343         );
344         if (wrk.readStringValue("") == expectedValue) {
345           return;
346         }
347       } catch (e) {
348         // The key either doesn't have a value or doesn't exist
349         // In either case we need to write it.
350       }
351       wrk.writeStringValue("", expectedValue);
352     } catch (e) {
353       // The method fails if we can't access the key
354       // which means it doesn't exist. That's a normal situation.
355       // We don't need to do anything here.
356     } finally {
357       wrk.close();
358     }
359   },