summaryrefslogtreecommitdiffstats
path: root/toolkit/system
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/system
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--toolkit/system/androidproxy/components.conf13
-rw-r--r--toolkit/system/androidproxy/moz.build18
-rw-r--r--toolkit/system/androidproxy/nsAndroidSystemProxySettings.cpp51
-rw-r--r--toolkit/system/commonproxy/ProxyUtils.cpp162
-rw-r--r--toolkit/system/commonproxy/ProxyUtils.h21
-rw-r--r--toolkit/system/commonproxy/moz.build18
-rw-r--r--toolkit/system/commonproxy/tests/gtest/TestProxyBypassRules.cpp47
-rw-r--r--toolkit/system/commonproxy/tests/gtest/moz.build18
-rw-r--r--toolkit/system/gnome/components.conf28
-rw-r--r--toolkit/system/gnome/moz.build31
-rw-r--r--toolkit/system/gnome/nsAlertsIconListener.cpp363
-rw-r--r--toolkit/system/gnome/nsAlertsIconListener.h105
-rw-r--r--toolkit/system/gnome/nsGIOService.cpp951
-rw-r--r--toolkit/system/gnome/nsGIOService.h26
-rw-r--r--toolkit/system/gnome/nsGSettingsService.cpp316
-rw-r--r--toolkit/system/gnome/nsGSettingsService.h29
-rw-r--r--toolkit/system/gnome/nsSystemAlertsService.cpp131
-rw-r--r--toolkit/system/gnome/nsSystemAlertsService.h42
-rw-r--r--toolkit/system/osxproxy/components.conf13
-rw-r--r--toolkit/system/osxproxy/moz.build23
-rw-r--r--toolkit/system/osxproxy/nsOSXSystemProxySettings.mm428
-rw-r--r--toolkit/system/unixproxy/components.conf13
-rw-r--r--toolkit/system/unixproxy/moz.build24
-rw-r--r--toolkit/system/unixproxy/nsLibProxySettings.cpp104
-rw-r--r--toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp405
-rw-r--r--toolkit/system/windowsDHCPClient/DHCPUtils.cpp247
-rw-r--r--toolkit/system/windowsDHCPClient/DHCPUtils.h31
-rw-r--r--toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.cpp41
-rw-r--r--toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.h48
-rw-r--r--toolkit/system/windowsDHCPClient/components.conf14
-rw-r--r--toolkit/system/windowsDHCPClient/moz.build22
-rw-r--r--toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.cpp77
-rw-r--r--toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.h34
-rw-r--r--toolkit/system/windowsDHCPClient/tests/gtest/TestDHCPUtils.cpp306
-rw-r--r--toolkit/system/windowsDHCPClient/tests/gtest/moz.build21
-rw-r--r--toolkit/system/windowsPackageManager/components.conf14
-rw-r--r--toolkit/system/windowsPackageManager/moz.build26
-rw-r--r--toolkit/system/windowsPackageManager/nsIWindowsPackageManager.idl42
-rw-r--r--toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp462
-rw-r--r--toolkit/system/windowsPackageManager/nsWindowsPackageManager.h28
-rw-r--r--toolkit/system/windowsPackageManager/tests/gtest/TestWindowsPackageManager.cpp35
-rw-r--r--toolkit/system/windowsPackageManager/tests/gtest/moz.build18
-rw-r--r--toolkit/system/windowsproxy/components.conf13
-rw-r--r--toolkit/system/windowsproxy/moz.build21
-rw-r--r--toolkit/system/windowsproxy/nsWindowsSystemProxySettings.cpp247
45 files changed, 5127 insertions, 0 deletions
diff --git a/toolkit/system/androidproxy/components.conf b/toolkit/system/androidproxy/components.conf
new file mode 100644
index 0000000000..499674c32b
--- /dev/null
+++ b/toolkit/system/androidproxy/components.conf
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{f01f0060-3708-478e-b935-3ec38be294b8}',
+ 'contract_ids': ['@mozilla.org/system-proxy-settings;1'],
+ 'type': 'nsAndroidSystemProxySettings',
+ },
+]
diff --git a/toolkit/system/androidproxy/moz.build b/toolkit/system/androidproxy/moz.build
new file mode 100644
index 0000000000..3d1a834676
--- /dev/null
+++ b/toolkit/system/androidproxy/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("GeckoView", "General")
+
+SOURCES += [
+ "nsAndroidSystemProxySettings.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/toolkit/system/androidproxy/nsAndroidSystemProxySettings.cpp b/toolkit/system/androidproxy/nsAndroidSystemProxySettings.cpp
new file mode 100644
index 0000000000..e60d0f3821
--- /dev/null
+++ b/toolkit/system/androidproxy/nsAndroidSystemProxySettings.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISystemProxySettings.h"
+#include "mozilla/Components.h"
+#include "nsPrintfCString.h"
+#include "nsNetCID.h"
+#include "nsISupports.h"
+
+#include "AndroidBridge.h"
+
+class nsAndroidSystemProxySettings : public nsISystemProxySettings {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISYSTEMPROXYSETTINGS
+
+ nsAndroidSystemProxySettings(){};
+
+ private:
+ virtual ~nsAndroidSystemProxySettings() {}
+};
+
+NS_IMPL_ISUPPORTS(nsAndroidSystemProxySettings, nsISystemProxySettings)
+
+NS_IMETHODIMP
+nsAndroidSystemProxySettings::GetMainThreadOnly(bool* aMainThreadOnly) {
+ *aMainThreadOnly = true;
+ return NS_OK;
+}
+
+nsresult nsAndroidSystemProxySettings::GetPACURI(nsACString& aResult) {
+ return NS_OK;
+}
+
+nsresult nsAndroidSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
+ const nsACString& aScheme,
+ const nsACString& aHost,
+ const int32_t aPort,
+ nsACString& aResult) {
+ return mozilla::AndroidBridge::Bridge()->GetProxyForURI(aSpec, aScheme, aHost,
+ aPort, aResult);
+}
+
+void test(){};
+
+NS_IMPL_COMPONENT_FACTORY(nsAndroidSystemProxySettings) {
+ return mozilla::MakeAndAddRef<nsAndroidSystemProxySettings>()
+ .downcast<nsISupports>();
+}
diff --git a/toolkit/system/commonproxy/ProxyUtils.cpp b/toolkit/system/commonproxy/ProxyUtils.cpp
new file mode 100644
index 0000000000..2c110db38b
--- /dev/null
+++ b/toolkit/system/commonproxy/ProxyUtils.cpp
@@ -0,0 +1,162 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ProxyUtils.h"
+
+#include "mozilla/IntegerRange.h"
+#include "nsReadableUtils.h"
+#include "nsTArray.h"
+#include "prnetdb.h"
+#include "prtypes.h"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+
+/**
+ * Normalize the short IP form into the complete form.
+ * For example, it converts "192.168" into "192.168.0.0"
+ */
+static void NormalizeAddr(const nsACString& aAddr, nsCString& aNormalized) {
+ nsTArray<nsCString> addr;
+ ParseString(aAddr, '.', addr);
+ aNormalized =
+ StringJoin("."_ns, IntegerRange(4), [&addr](nsACString& dst, size_t i) {
+ if (i < addr.Length()) {
+ dst.Append(addr[i]);
+ } else {
+ dst.Append('0');
+ }
+ });
+}
+
+static PRUint32 MaskIPv4Addr(PRUint32 aAddr, uint16_t aMaskLen) {
+ if (aMaskLen == 32) {
+ return aAddr;
+ }
+ return PR_htonl(PR_ntohl(aAddr) & (~0L << (32 - aMaskLen)));
+}
+
+static void MaskIPv6Addr(PRIPv6Addr& aAddr, uint16_t aMaskLen) {
+ if (aMaskLen == 128) {
+ return;
+ }
+
+ if (aMaskLen > 96) {
+ aAddr.pr_s6_addr32[3] =
+ PR_htonl(PR_ntohl(aAddr.pr_s6_addr32[3]) & (~0L << (128 - aMaskLen)));
+ } else if (aMaskLen > 64) {
+ aAddr.pr_s6_addr32[3] = 0;
+ aAddr.pr_s6_addr32[2] =
+ PR_htonl(PR_ntohl(aAddr.pr_s6_addr32[2]) & (~0L << (96 - aMaskLen)));
+ } else if (aMaskLen > 32) {
+ aAddr.pr_s6_addr32[3] = 0;
+ aAddr.pr_s6_addr32[2] = 0;
+ aAddr.pr_s6_addr32[1] =
+ PR_htonl(PR_ntohl(aAddr.pr_s6_addr32[1]) & (~0L << (64 - aMaskLen)));
+ } else {
+ aAddr.pr_s6_addr32[3] = 0;
+ aAddr.pr_s6_addr32[2] = 0;
+ aAddr.pr_s6_addr32[1] = 0;
+ aAddr.pr_s6_addr32[0] =
+ PR_htonl(PR_ntohl(aAddr.pr_s6_addr32[0]) & (~0L << (32 - aMaskLen)));
+ }
+
+ return;
+}
+
+static bool IsMatchMask(const nsACString& aHost, const nsACString& aOverride) {
+ nsresult rv;
+
+ auto tokenEnd = aOverride.FindChar('/');
+ if (tokenEnd == -1) {
+ return false;
+ }
+
+ nsAutoCString prefixStr(
+ Substring(aOverride, tokenEnd + 1, aOverride.Length() - tokenEnd - 1));
+ auto maskLen = prefixStr.ToInteger(&rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ nsAutoCString override(aOverride);
+ NormalizeAddr(Substring(aOverride, 0, tokenEnd), override);
+
+ PRNetAddr prAddrHost;
+ PRNetAddr prAddrOverride;
+ if (PR_SUCCESS !=
+ PR_StringToNetAddr(PromiseFlatCString(aHost).get(), &prAddrHost) ||
+ PR_SUCCESS != PR_StringToNetAddr(override.get(), &prAddrOverride)) {
+ return false;
+ }
+
+ if (prAddrHost.raw.family == PR_AF_INET &&
+ prAddrOverride.raw.family == PR_AF_INET) {
+ return MaskIPv4Addr(prAddrHost.inet.ip, maskLen) ==
+ MaskIPv4Addr(prAddrOverride.inet.ip, maskLen);
+ } else if (prAddrHost.raw.family == PR_AF_INET6 &&
+ prAddrOverride.raw.family == PR_AF_INET6) {
+ MaskIPv6Addr(prAddrHost.ipv6.ip, maskLen);
+ MaskIPv6Addr(prAddrOverride.ipv6.ip, maskLen);
+
+ return memcmp(&prAddrHost.ipv6.ip, &prAddrOverride.ipv6.ip,
+ sizeof(PRIPv6Addr)) == 0;
+ }
+
+ return false;
+}
+
+static bool IsMatchWildcard(const nsACString& aHost,
+ const nsACString& aOverride) {
+ nsAutoCString host(aHost);
+ nsAutoCString override(aOverride);
+
+ int32_t overrideLength = override.Length();
+ int32_t tokenStart = 0;
+ int32_t offset = 0;
+ bool star = false;
+
+ while (tokenStart < overrideLength) {
+ int32_t tokenEnd = override.FindChar('*', tokenStart);
+ if (tokenEnd == tokenStart) {
+ // Star is the first character in the token.
+ star = true;
+ tokenStart++;
+ // If the character following the '*' is a '.' character then skip
+ // it so that "*.foo.com" allows "foo.com".
+ if (override.FindChar('.', tokenStart) == tokenStart) {
+ nsAutoCString token(Substring(override, tokenStart + 1,
+ overrideLength - tokenStart - 1));
+ if (host.Equals(token)) {
+ return true;
+ }
+ }
+ } else {
+ if (tokenEnd == -1) {
+ tokenEnd = overrideLength; // no '*' char, match rest of string
+ }
+ nsAutoCString token(
+ Substring(override, tokenStart, tokenEnd - tokenStart));
+ offset = host.Find(token, offset);
+ if (offset == -1 || (!star && offset)) {
+ return false;
+ }
+ star = false;
+ tokenStart = tokenEnd;
+ offset += token.Length();
+ }
+ }
+
+ return (star || (offset == static_cast<int32_t>(host.Length())));
+}
+
+bool IsHostProxyEntry(const nsACString& aHost, const nsACString& aOverride) {
+ return IsMatchMask(aHost, aOverride) || IsMatchWildcard(aHost, aOverride);
+}
+
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
diff --git a/toolkit/system/commonproxy/ProxyUtils.h b/toolkit/system/commonproxy/ProxyUtils.h
new file mode 100644
index 0000000000..16c451c547
--- /dev/null
+++ b/toolkit/system/commonproxy/ProxyUtils.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_toolkit_system_commonproxy_ProxyUtils_h
+#define mozilla_toolkit_system_commonproxy_ProxyUtils_h
+
+#include "nsString.h"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+
+bool IsHostProxyEntry(const nsACString& aHost, const nsACString& aOverride);
+
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
+
+#endif // mozilla_toolkit_system_commonproxy_ProxyUtils_h
diff --git a/toolkit/system/commonproxy/moz.build b/toolkit/system/commonproxy/moz.build
new file mode 100644
index 0000000000..6f93b46e9a
--- /dev/null
+++ b/toolkit/system/commonproxy/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking")
+
+TEST_DIRS += ["tests/gtest"]
+
+SOURCES += ["ProxyUtils.cpp"]
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+]
diff --git a/toolkit/system/commonproxy/tests/gtest/TestProxyBypassRules.cpp b/toolkit/system/commonproxy/tests/gtest/TestProxyBypassRules.cpp
new file mode 100644
index 0000000000..3047088d62
--- /dev/null
+++ b/toolkit/system/commonproxy/tests/gtest/TestProxyBypassRules.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "ProxyUtils.h"
+
+using namespace mozilla::toolkit::system;
+
+TEST(CommonProxy, TestProxyBypassRules)
+{
+ EXPECT_TRUE(IsHostProxyEntry("mozilla.org"_ns, "mozilla.org"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("mozilla.org"_ns, "*mozilla.org"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("mozilla.org"_ns, "*.mozilla.org"_ns));
+ EXPECT_FALSE(IsHostProxyEntry("notmozilla.org"_ns, "*.mozilla.org"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("www.mozilla.org"_ns, "*mozilla.org"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("www.mozilla.org"_ns, "*.mozilla.org"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("www.mozilla.com"_ns, "*.mozilla.*"_ns));
+}
+
+TEST(CommonProxy, TestProxyBypassRulesIPv4)
+{
+ EXPECT_TRUE(IsHostProxyEntry("192.168.1.1"_ns, "192.168.1.*"_ns));
+ EXPECT_FALSE(IsHostProxyEntry("192.168.1.1"_ns, "192.168.2.*"_ns));
+
+ EXPECT_TRUE(IsHostProxyEntry("10.1.2.3"_ns, "10.0.0.0/8"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("192.168.192.1"_ns, "192.168/16"_ns));
+ EXPECT_FALSE(IsHostProxyEntry("192.168.192.1"_ns, "192.168/17"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("192.168.192.1"_ns, "192.168.128/17"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("192.168.1.1"_ns, "192.168.1.1/32"_ns));
+}
+
+TEST(CommonProxy, TestProxyBypassRulesIPv6)
+{
+ EXPECT_TRUE(IsHostProxyEntry("2001:0DB8:ABCD:0012:0123:4567:89AB:CDEF"_ns,
+ "2001:db8:abcd:0012::0/64"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("2001:0DB8:ABCD:0012:0000:4567:89AB:CDEF"_ns,
+ "2001:db8:abcd:0012::0/80"_ns));
+ EXPECT_FALSE(IsHostProxyEntry("2001:0DB8:ABCD:0012:0123:4567:89AB:CDEF"_ns,
+ "2001:db8:abcd:0012::0/80"_ns));
+ EXPECT_TRUE(IsHostProxyEntry("2001:0DB8:ABCD:0012:0000:0000:89AB:CDEF"_ns,
+ "2001:db8:abcd:0012::0/96"_ns));
+ EXPECT_FALSE(IsHostProxyEntry("2001:0DB8:ABCD:0012:0123:4567:89AB:CDEF"_ns,
+ "2001:db8:abcd:0012::0/96"_ns));
+}
diff --git a/toolkit/system/commonproxy/tests/gtest/moz.build b/toolkit/system/commonproxy/tests/gtest/moz.build
new file mode 100644
index 0000000000..3df0793605
--- /dev/null
+++ b/toolkit/system/commonproxy/tests/gtest/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ "TestProxyBypassRules.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "../..",
+]
+
+FINAL_LIBRARY = "xul-gtest"
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wshadow"]
diff --git a/toolkit/system/gnome/components.conf b/toolkit/system/gnome/components.conf
new file mode 100644
index 0000000000..1aa0ab1048
--- /dev/null
+++ b/toolkit/system/gnome/components.conf
@@ -0,0 +1,28 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{e3a1f3c9-3ae1-4b40-a5e0-7b457fc9a9ad}',
+ 'contract_ids': ['@mozilla.org/gio-service;1'],
+ 'type': 'nsGIOService',
+ 'headers': ['/toolkit/system/gnome/nsGIOService.h'],
+ },
+ {
+ 'cid': '{bfd4a9d8-d886-4161-81ef-8868da114170}',
+ 'contract_ids': ['@mozilla.org/gsettings-service;1'],
+ 'type': 'nsGSettingsService',
+ 'headers': ['/toolkit/system/gnome/nsGSettingsService.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{84e11f80-ca55-11dd-ad8b-0800200c9a66}',
+ 'contract_ids': ['@mozilla.org/system-alerts-service;1'],
+ 'type': 'nsSystemAlertsService',
+ 'headers': ['/toolkit/system/gnome/nsSystemAlertsService.h'],
+ 'init_method': 'Init',
+ },
+]
diff --git a/toolkit/system/gnome/moz.build b/toolkit/system/gnome/moz.build
new file mode 100644
index 0000000000..aa2df2b66b
--- /dev/null
+++ b/toolkit/system/gnome/moz.build
@@ -0,0 +1,31 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Firefox", "Shell Integration")
+
+SOURCES += [
+ "nsAlertsIconListener.cpp",
+ "nsSystemAlertsService.cpp",
+]
+
+SOURCES += [
+ "nsGIOService.cpp",
+ "nsGSettingsService.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/toolkit/components/build/",
+]
+
+CXXFLAGS += CONFIG["GLIB_CFLAGS"]
+CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
diff --git a/toolkit/system/gnome/nsAlertsIconListener.cpp b/toolkit/system/gnome/nsAlertsIconListener.cpp
new file mode 100644
index 0000000000..06152fd4d5
--- /dev/null
+++ b/toolkit/system/gnome/nsAlertsIconListener.cpp
@@ -0,0 +1,363 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAlertsIconListener.h"
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSystemAlertsService.h"
+#include "nsIAlertsService.h"
+#include "nsICancelable.h"
+#include "nsImageToPixbuf.h"
+#include "nsIStringBundle.h"
+#include "nsIObserverService.h"
+#include "nsCRT.h"
+#include "mozilla/XREAppData.h"
+#include "mozilla/GRefPtr.h"
+#include "mozilla/GUniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+#include <dlfcn.h>
+#include <gdk/gdk.h>
+
+using namespace mozilla;
+extern const StaticXREAppData* gAppData;
+
+static bool gHasActions = false;
+static bool gHasCaps = false;
+
+void* nsAlertsIconListener::libNotifyHandle = nullptr;
+bool nsAlertsIconListener::libNotifyNotAvail = false;
+nsAlertsIconListener::notify_is_initted_t
+ nsAlertsIconListener::notify_is_initted = nullptr;
+nsAlertsIconListener::notify_init_t nsAlertsIconListener::notify_init = nullptr;
+nsAlertsIconListener::notify_get_server_caps_t
+ nsAlertsIconListener::notify_get_server_caps = nullptr;
+nsAlertsIconListener::notify_notification_new_t
+ nsAlertsIconListener::notify_notification_new = nullptr;
+nsAlertsIconListener::notify_notification_show_t
+ nsAlertsIconListener::notify_notification_show = nullptr;
+nsAlertsIconListener::notify_notification_set_icon_from_pixbuf_t
+ nsAlertsIconListener::notify_notification_set_icon_from_pixbuf = nullptr;
+nsAlertsIconListener::notify_notification_add_action_t
+ nsAlertsIconListener::notify_notification_add_action = nullptr;
+nsAlertsIconListener::notify_notification_close_t
+ nsAlertsIconListener::notify_notification_close = nullptr;
+nsAlertsIconListener::notify_notification_set_hint_t
+ nsAlertsIconListener::notify_notification_set_hint = nullptr;
+nsAlertsIconListener::notify_notification_set_timeout_t
+ nsAlertsIconListener::notify_notification_set_timeout = nullptr;
+
+static void notify_action_cb(NotifyNotification* notification, gchar* action,
+ gpointer user_data) {
+ nsAlertsIconListener* alert = static_cast<nsAlertsIconListener*>(user_data);
+ alert->SendCallback();
+}
+
+static void notify_closed_marshal(GClosure* closure, GValue* return_value,
+ guint n_param_values,
+ const GValue* param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data) {
+ MOZ_ASSERT(n_param_values >= 1, "No object in params");
+
+ nsAlertsIconListener* alert =
+ static_cast<nsAlertsIconListener*>(closure->data);
+ alert->SendClosed();
+ NS_RELEASE(alert);
+}
+
+static already_AddRefed<GdkPixbuf> GetPixbufFromImgRequest(
+ imgIRequest* aRequest) {
+ nsCOMPtr<imgIContainer> image;
+ nsresult rv = aRequest->GetImage(getter_AddRefs(image));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ int32_t width = 0, height = 0;
+ const int32_t kBytesPerPixel = 4;
+ // DBUS_MAXIMUM_ARRAY_LENGTH is 64M, there is 60 bytes overhead
+ // for the hints array with only the image payload, 256 is used to give
+ // some breathing room.
+ const int32_t kMaxImageBytes = 64 * 1024 * 1024 - 256;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+ if (width * height * kBytesPerPixel > kMaxImageBytes) {
+ // The image won't fit in a dbus array
+ return nullptr;
+ }
+
+ return nsImageToPixbuf::ImageToPixbuf(image);
+}
+
+NS_IMPL_ISUPPORTS(nsAlertsIconListener, nsIAlertNotificationImageListener,
+ nsIObserver, nsISupportsWeakReference)
+
+nsAlertsIconListener::nsAlertsIconListener(nsSystemAlertsService* aBackend,
+ const nsAString& aAlertName)
+ : mAlertName(aAlertName), mBackend(aBackend), mNotification(nullptr) {
+ if (!libNotifyHandle && !libNotifyNotAvail) {
+ libNotifyHandle = dlopen("libnotify.so.4", RTLD_LAZY);
+ if (!libNotifyHandle) {
+ libNotifyHandle = dlopen("libnotify.so.1", RTLD_LAZY);
+ if (!libNotifyHandle) {
+ libNotifyNotAvail = true;
+ return;
+ }
+ }
+
+ notify_is_initted =
+ (notify_is_initted_t)dlsym(libNotifyHandle, "notify_is_initted");
+ notify_init = (notify_init_t)dlsym(libNotifyHandle, "notify_init");
+ notify_get_server_caps = (notify_get_server_caps_t)dlsym(
+ libNotifyHandle, "notify_get_server_caps");
+ notify_notification_new = (notify_notification_new_t)dlsym(
+ libNotifyHandle, "notify_notification_new");
+ notify_notification_show = (notify_notification_show_t)dlsym(
+ libNotifyHandle, "notify_notification_show");
+ notify_notification_set_icon_from_pixbuf =
+ (notify_notification_set_icon_from_pixbuf_t)dlsym(
+ libNotifyHandle, "notify_notification_set_icon_from_pixbuf");
+ notify_notification_add_action = (notify_notification_add_action_t)dlsym(
+ libNotifyHandle, "notify_notification_add_action");
+ notify_notification_close = (notify_notification_close_t)dlsym(
+ libNotifyHandle, "notify_notification_close");
+ notify_notification_set_hint = (notify_notification_set_hint_t)dlsym(
+ libNotifyHandle, "notify_notification_set_hint");
+ notify_notification_set_timeout = (notify_notification_set_timeout_t)dlsym(
+ libNotifyHandle, "notify_notification_set_timeout");
+ if (!notify_is_initted || !notify_init || !notify_get_server_caps ||
+ !notify_notification_new || !notify_notification_show ||
+ !notify_notification_set_icon_from_pixbuf ||
+ !notify_notification_add_action || !notify_notification_close) {
+ dlclose(libNotifyHandle);
+ libNotifyHandle = nullptr;
+ }
+ }
+}
+
+nsAlertsIconListener::~nsAlertsIconListener() {
+ mBackend->RemoveListener(mAlertName, this);
+ // Don't dlclose libnotify as it uses atexit().
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::OnImageMissing(nsISupports*) {
+ // This notification doesn't have an image, or there was an error getting
+ // the image. Show the notification without an icon.
+ return ShowAlert(nullptr);
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::OnImageReady(nsISupports*, imgIRequest* aRequest) {
+ RefPtr<GdkPixbuf> imagePixbuf = GetPixbufFromImgRequest(aRequest);
+ ShowAlert(imagePixbuf);
+ return NS_OK;
+}
+
+nsresult nsAlertsIconListener::ShowAlert(GdkPixbuf* aPixbuf) {
+ if (!mBackend->IsActiveListener(mAlertName, this)) return NS_OK;
+
+ mNotification = notify_notification_new(mAlertTitle.get(), mAlertText.get(),
+ nullptr, nullptr);
+
+ if (!mNotification) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIObserverService> obsServ =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (obsServ) obsServ->AddObserver(this, "quit-application", true);
+
+ if (aPixbuf) notify_notification_set_icon_from_pixbuf(mNotification, aPixbuf);
+
+ NS_ADDREF(this);
+ if (mAlertHasAction) {
+ // What we put as the label doesn't matter here, if the action
+ // string is "default" then that makes the entire bubble clickable
+ // rather than creating a button.
+ notify_notification_add_action(mNotification, "default", "Activate",
+ notify_action_cb, this, nullptr);
+ }
+
+ if (notify_notification_set_hint) {
+ notify_notification_set_hint(mNotification, "suppress-sound",
+ g_variant_new_boolean(mAlertIsSilent));
+
+ // If MOZ_DESKTOP_FILE_NAME variable is set, use it as the application id,
+ // otherwise use gAppData->name
+ if (getenv("MOZ_DESKTOP_FILE_NAME")) {
+ // Send the desktop name to identify the application
+ // The desktop-entry is the part before the .desktop
+ notify_notification_set_hint(
+ mNotification, "desktop-entry",
+ g_variant_new("s", getenv("MOZ_DESKTOP_FILE_NAME")));
+ } else {
+ notify_notification_set_hint(mNotification, "desktop-entry",
+ g_variant_new("s", gAppData->remotingName));
+ }
+ }
+
+ if (notify_notification_set_timeout && mAlertRequiresInteraction) {
+ constexpr gint kNotifyExpiresNever = 0;
+ notify_notification_set_timeout(mNotification, kNotifyExpiresNever);
+ }
+
+ // Fedora 10 calls NotifyNotification "closed" signal handlers with a
+ // different signature, so a marshaller is used instead of a C callback to
+ // get the user_data (this) in a parseable format. |closure| is created
+ // with a floating reference, which gets sunk by g_signal_connect_closure().
+ GClosure* closure = g_closure_new_simple(sizeof(GClosure), this);
+ g_closure_set_marshal(closure, notify_closed_marshal);
+ mClosureHandler =
+ g_signal_connect_closure(mNotification, "closed", closure, FALSE);
+ GUniquePtr<GError> error;
+ if (!notify_notification_show(mNotification, getter_Transfers(error))) {
+ NS_WARNING(error->message);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mAlertListener)
+ mAlertListener->Observe(nullptr, "alertshow", mAlertCookie.get());
+
+ return NS_OK;
+}
+
+void nsAlertsIconListener::SendCallback() {
+ if (mAlertListener)
+ mAlertListener->Observe(nullptr, "alertclickcallback", mAlertCookie.get());
+}
+
+void nsAlertsIconListener::SendClosed() {
+ if (mNotification) {
+ g_object_unref(mNotification);
+ mNotification = nullptr;
+ }
+ NotifyFinished();
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ // We need to close any open notifications upon application exit, otherwise
+ // we will leak since libnotify holds a ref for us.
+ if (!nsCRT::strcmp(aTopic, "quit-application") && mNotification) {
+ g_signal_handler_disconnect(mNotification, mClosureHandler);
+ g_object_unref(mNotification);
+ mNotification = nullptr;
+ Release(); // equivalent to NS_RELEASE(this)
+ }
+ return NS_OK;
+}
+
+nsresult nsAlertsIconListener::Close() {
+ if (mIconRequest) {
+ mIconRequest->Cancel(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ }
+
+ if (!mNotification) {
+ NotifyFinished();
+ return NS_OK;
+ }
+
+ GUniquePtr<GError> error;
+ if (!notify_notification_close(mNotification, getter_Transfers(error))) {
+ NS_WARNING(error->message);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAlertsIconListener::InitAlertAsync(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener) {
+ if (!libNotifyHandle) return NS_ERROR_FAILURE;
+
+ if (!notify_is_initted()) {
+ // Give the name of this application to libnotify
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+
+ nsAutoCString appShortName;
+ if (bundleService) {
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(bundle));
+ nsAutoString appName;
+
+ if (bundle) {
+ bundle->GetStringFromName("brandShortName", appName);
+ CopyUTF16toUTF8(appName, appShortName);
+ } else {
+ NS_WARNING(
+ "brand.properties not present, using default application name");
+ appShortName.AssignLiteral("Mozilla");
+ }
+ } else {
+ appShortName.AssignLiteral("Mozilla");
+ }
+
+ if (!notify_init(appShortName.get())) return NS_ERROR_FAILURE;
+
+ GList* server_caps = notify_get_server_caps();
+ if (server_caps) {
+ gHasCaps = true;
+ for (GList* cap = server_caps; cap != nullptr; cap = cap->next) {
+ if (!strcmp((char*)cap->data, "actions")) {
+ gHasActions = true;
+ break;
+ }
+ }
+ g_list_foreach(server_caps, (GFunc)g_free, nullptr);
+ g_list_free(server_caps);
+ }
+ }
+
+ if (!gHasCaps) {
+ // if notify_get_server_caps() failed above we need to assume
+ // there is no notification-server to display anything
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = aAlert->GetTextClickable(&mAlertHasAction);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!gHasActions && mAlertHasAction)
+ return NS_ERROR_FAILURE; // No good, fallback to XUL
+
+ rv = aAlert->GetSilent(&mAlertIsSilent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aAlert->GetRequireInteraction(&mAlertRequiresInteraction);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString title;
+ rv = aAlert->GetTitle(title);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Workaround for a libnotify bug - blank titles aren't dealt with
+ // properly so we use a space
+ if (title.IsEmpty()) {
+ mAlertTitle = " "_ns;
+ } else {
+ CopyUTF16toUTF8(title, mAlertTitle);
+ }
+
+ nsAutoString text;
+ rv = aAlert->GetText(text);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF16toUTF8(text, mAlertText);
+
+ mAlertListener = aAlertListener;
+
+ rv = aAlert->GetCookie(mAlertCookie);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aAlert->LoadImage(/* aTimeout = */ 0, this, /* aUserData = */ nullptr,
+ getter_AddRefs(mIconRequest));
+}
+
+void nsAlertsIconListener::NotifyFinished() {
+ if (mAlertListener)
+ mAlertListener->Observe(nullptr, "alertfinished", mAlertCookie.get());
+}
diff --git a/toolkit/system/gnome/nsAlertsIconListener.h b/toolkit/system/gnome/nsAlertsIconListener.h
new file mode 100644
index 0000000000..e0ef6cffe1
--- /dev/null
+++ b/toolkit/system/gnome/nsAlertsIconListener.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAlertsIconListener_h__
+#define nsAlertsIconListener_h__
+
+#include "nsCOMPtr.h"
+#include "nsIAlertsService.h"
+#include "nsString.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+class nsIAlertNotification;
+class nsICancelable;
+class nsSystemAlertsService;
+
+struct NotifyNotification;
+
+class nsAlertsIconListener : public nsIAlertNotificationImageListener,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIALERTNOTIFICATIONIMAGELISTENER
+ NS_DECL_NSIOBSERVER
+
+ nsAlertsIconListener(nsSystemAlertsService* aBackend,
+ const nsAString& aAlertName);
+
+ nsresult InitAlertAsync(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener);
+ nsresult Close();
+
+ void SendCallback();
+ void SendClosed();
+
+ protected:
+ virtual ~nsAlertsIconListener();
+
+ /**
+ * The only difference between libnotify.so.4 and libnotify.so.1 for these
+ * symbols is that notify_notification_new takes three arguments in
+ * libnotify.so.4 and four in libnotify.so.1. Passing the fourth argument as
+ * NULL is binary compatible.
+ */
+ using NotifyActionCallback = void (*)(NotifyNotification*, char*, gpointer);
+ using notify_is_initted_t = bool (*)();
+ using notify_init_t = bool (*)(const char*);
+ using notify_get_server_caps_t = GList* (*)();
+ using notify_notification_new_t = NotifyNotification* (*)(const char*,
+ const char*,
+ const char*,
+ const char*);
+ using notify_notification_show_t = bool (*)(void*, GError**);
+ using notify_notification_set_icon_from_pixbuf_t = void (*)(void*,
+ GdkPixbuf*);
+ using notify_notification_add_action_t = void (*)(void*, const char*,
+ const char*,
+ NotifyActionCallback,
+ gpointer, GFreeFunc);
+ using notify_notification_close_t = bool (*)(void*, GError**);
+ using notify_notification_set_hint_t = void (*)(NotifyNotification*,
+ const char*, GVariant*);
+ using notify_notification_set_timeout_t = void (*)(NotifyNotification*, gint);
+
+ nsCOMPtr<nsICancelable> mIconRequest;
+ nsCString mAlertTitle;
+ nsCString mAlertText;
+
+ nsCOMPtr<nsIObserver> mAlertListener;
+ nsString mAlertCookie;
+ nsString mAlertName;
+
+ RefPtr<nsSystemAlertsService> mBackend;
+
+ bool mAlertHasAction;
+ bool mAlertIsSilent;
+ bool mAlertRequiresInteraction;
+
+ static void* libNotifyHandle;
+ static bool libNotifyNotAvail;
+ static notify_is_initted_t notify_is_initted;
+ static notify_init_t notify_init;
+ static notify_get_server_caps_t notify_get_server_caps;
+ static notify_notification_new_t notify_notification_new;
+ static notify_notification_show_t notify_notification_show;
+ static notify_notification_set_icon_from_pixbuf_t
+ notify_notification_set_icon_from_pixbuf;
+ static notify_notification_add_action_t notify_notification_add_action;
+ static notify_notification_close_t notify_notification_close;
+ static notify_notification_set_hint_t notify_notification_set_hint;
+ static notify_notification_set_timeout_t notify_notification_set_timeout;
+ NotifyNotification* mNotification;
+ gulong mClosureHandler;
+
+ nsresult ShowAlert(GdkPixbuf* aPixbuf);
+
+ void NotifyFinished();
+};
+
+#endif
diff --git a/toolkit/system/gnome/nsGIOService.cpp b/toolkit/system/gnome/nsGIOService.cpp
new file mode 100644
index 0000000000..d02560b68f
--- /dev/null
+++ b/toolkit/system/gnome/nsGIOService.cpp
@@ -0,0 +1,951 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsGIOService.h"
+#include "nsString.h"
+#include "nsIURI.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+#include "nsStringEnumerator.h"
+#include "nsIMIMEInfo.h"
+#include "nsComponentManagerUtils.h"
+#include "nsArray.h"
+#include "nsPrintfCString.h"
+#include "mozilla/GRefPtr.h"
+#include "mozilla/GUniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/WidgetUtilsGtk.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/net/DNS.h"
+#include "prenv.h"
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#ifdef MOZ_ENABLE_DBUS
+# include <fcntl.h>
+# include <dlfcn.h>
+# include "mozilla/widget/AsyncDBus.h"
+# include "mozilla/WidgetUtilsGtk.h"
+#endif
+
+using namespace mozilla;
+
+class nsFlatpakHandlerApp : public nsIHandlerApp {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+ nsFlatpakHandlerApp() = default;
+
+ private:
+ virtual ~nsFlatpakHandlerApp() = default;
+};
+
+NS_IMPL_ISUPPORTS(nsFlatpakHandlerApp, nsIHandlerApp)
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::GetName(nsAString& aName) {
+ aName.AssignLiteral("System Handler");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::SetName(const nsAString& aName) {
+ // We don't implement SetName because flatpak system handler name is fixed
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::GetDetailedDescription(nsAString& aDetailedDescription) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::SetDetailedDescription(
+ const nsAString& aDetailedDescription) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::Equals(nsIHandlerApp* aHandlerApp, bool* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::LaunchWithURI(
+ nsIURI* aUri, mozilla::dom::BrowsingContext* aBrowsingContext) {
+ nsCString spec;
+ aUri->GetSpec(spec);
+ GUniquePtr<GError> error;
+
+ // The TMPDIR where files are downloaded when user choose to open them
+ // needs to be accessible from sandbox and host. The default settings
+ // TMPDIR=/tmp is accessible only to the sandbox. That can be the reason
+ // why the gtk_show_uri fails there.
+ // The workaround is to set TMPDIR environment variable in sandbox to
+ // $XDG_CACHE_HOME/tmp before executing Firefox.
+ gtk_show_uri(nullptr, spec.get(), GDK_CURRENT_TIME, getter_Transfers(error));
+ if (error) {
+ NS_WARNING(
+ nsPrintfCString("Cannot launch flatpak handler: %s", error->message)
+ .get());
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+/**
+ * Get command without any additional arguments
+ * @param aCommandWithArguments full commandline input string
+ * @param aCommand string for storing command without arguments
+ * @return NS_ERROR_FAILURE when unable to parse commandline
+ */
+static nsresult GetCommandFromCommandline(
+ nsACString const& aCommandWithArguments, nsACString& aCommand) {
+ GUniquePtr<GError> error;
+ gchar** argv = nullptr;
+ if (!g_shell_parse_argv(aCommandWithArguments.BeginReading(), nullptr, &argv,
+ getter_Transfers(error)) ||
+ !argv[0]) {
+ g_warning("Cannot parse command with arguments: %s", error->message);
+ g_strfreev(argv);
+ return NS_ERROR_FAILURE;
+ }
+ aCommand.Assign(argv[0]);
+ g_strfreev(argv);
+ return NS_OK;
+}
+
+class nsGIOMimeApp final : public nsIGIOMimeApp {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+ NS_DECL_NSIGIOMIMEAPP
+
+ explicit nsGIOMimeApp(already_AddRefed<GAppInfo> aApp) : mApp(aApp) {}
+
+ private:
+ ~nsGIOMimeApp() = default;
+
+ RefPtr<GAppInfo> mApp;
+};
+
+NS_IMPL_ISUPPORTS(nsGIOMimeApp, nsIGIOMimeApp, nsIHandlerApp)
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetId(nsACString& aId) {
+ aId.Assign(g_app_info_get_id(mApp));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetName(nsAString& aName) {
+ aName.Assign(NS_ConvertUTF8toUTF16(g_app_info_get_name(mApp)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::SetName(const nsAString& aName) {
+ // We don't implement SetName because we're using mGIOMimeApp instance for
+ // obtaining application name
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetCommand(nsACString& aCommand) {
+ const char* cmd = g_app_info_get_commandline(mApp);
+ if (!cmd) return NS_ERROR_FAILURE;
+ aCommand.Assign(cmd);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetExpectsURIs(int32_t* aExpects) {
+ *aExpects = g_app_info_supports_uris(mApp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetDetailedDescription(nsAString& aDetailedDescription) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::SetDetailedDescription(const nsAString& aDetailedDescription) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::Equals(nsIHandlerApp* aHandlerApp, bool* _retval) {
+ if (!aHandlerApp) return NS_ERROR_FAILURE;
+
+ // Compare with nsILocalHandlerApp instance by name
+ nsCOMPtr<nsILocalHandlerApp> localHandlerApp = do_QueryInterface(aHandlerApp);
+ if (localHandlerApp) {
+ nsAutoString theirName;
+ nsAutoString thisName;
+ GetName(thisName);
+ localHandlerApp->GetName(theirName);
+ *_retval = thisName.Equals(theirName);
+ return NS_OK;
+ }
+
+ // Compare with nsIGIOMimeApp instance by command with stripped arguments
+ nsCOMPtr<nsIGIOMimeApp> gioMimeApp = do_QueryInterface(aHandlerApp);
+ if (gioMimeApp) {
+ nsAutoCString thisCommandline, thisCommand;
+ nsresult rv = GetCommand(thisCommandline);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetCommandFromCommandline(thisCommandline, thisCommand);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString theirCommandline, theirCommand;
+ gioMimeApp->GetCommand(theirCommandline);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetCommandFromCommandline(theirCommandline, theirCommand);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = thisCommand.Equals(theirCommand);
+ return NS_OK;
+ }
+
+ // We can only compare with nsILocalHandlerApp and nsGIOMimeApp
+ *_retval = false;
+ return NS_OK;
+}
+
+static RefPtr<GAppLaunchContext> GetLaunchContext(
+ const char* aXDGToken = nullptr) {
+ RefPtr<GAppLaunchContext> context = dont_AddRef(g_app_launch_context_new());
+ // Unset this before launching third-party MIME handlers. Otherwise, if
+ // Thunderbird sets this in its startup script (as it does in Debian and
+ // Fedora), and Firefox does not set this in its startup script (it doesn't in
+ // Debian), then Firefox will think it is part of Thunderbird and try to make
+ // Thunderbird the default browser. See bug 1494436.
+ g_app_launch_context_unsetenv(context, "MOZ_APP_LAUNCHER");
+ if (aXDGToken) {
+ g_app_launch_context_setenv(context, "XDG_ACTIVATION_TOKEN", aXDGToken);
+ }
+ return context;
+}
+
+#ifdef __OpenBSD__
+// wrappers required for OpenBSD sandboxing with unveil()
+gboolean g_app_info_launch_uris_openbsd(GAppInfo* mApp, const char* uri,
+ GAppLaunchContext* context,
+ GError** error) {
+ gchar* path = g_filename_from_uri(uri, NULL, NULL);
+ auto releasePath = MakeScopeExit([&] { g_free(path); });
+ const gchar* bin = g_app_info_get_executable(mApp);
+ if (!bin) {
+ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "no executable found for %s, maybe not unveiled ?",
+ g_app_info_get_name(mApp));
+ return FALSE;
+ }
+ g_debug("spawning %s %s for %s", bin, path, uri);
+ const gchar* const argv[] = {bin, path, NULL};
+
+ GSpawnFlags flags =
+ static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD);
+ gboolean result =
+ g_spawn_async(NULL, (char**)argv, NULL, flags, NULL, NULL, NULL, error);
+
+ if (!result) {
+ g_warning("Cannot launch application %s with arg %s: %s", bin, path,
+ (*error)->message);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+gboolean g_app_info_launch_default_for_uri_openbsd(const char* uri,
+ GAppLaunchContext* context,
+ GError** error) {
+ gboolean result_uncertain;
+ gchar* path = g_filename_from_uri(uri, NULL, NULL);
+ gchar* content_type = g_content_type_guess(path, NULL, 0, &result_uncertain);
+ gchar* scheme = g_uri_parse_scheme(uri);
+ auto release = MakeScopeExit([&] {
+ g_free(path);
+ g_free(content_type);
+ g_free(scheme);
+ });
+ if (g_strcmp0(scheme, "http") == 0 || g_strcmp0(scheme, "https") == 0)
+ return g_app_info_launch_default_for_uri(uri, context, error);
+
+ if (content_type != NULL && !result_uncertain) {
+ g_debug("content type for %s: %s", uri, content_type);
+ GAppInfo* app_info = g_app_info_get_default_for_type(content_type, false);
+ auto releaseAppInfo = MakeScopeExit([&] {
+ if (app_info) g_object_unref(app_info);
+ });
+ if (!app_info) {
+ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Could not find default handler for content type %s",
+ content_type);
+ return FALSE;
+ } else {
+ return g_app_info_launch_uris_openbsd(app_info, uri, context, error);
+ }
+ } else {
+ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Could not find content type for URI: %s", uri);
+ return FALSE;
+ }
+}
+#endif
+
+static NS_IMETHODIMP LaunchWithURIImpl(RefPtr<GAppInfo> aInfo, nsIURI* aUri,
+ const char* aXDGToken = nullptr) {
+ GList uris = {0};
+ nsCString spec;
+ aUri->GetSpec(spec);
+ // nsPromiseFlatCString flatUri(aUri);
+ uris.data = const_cast<char*>(spec.get());
+
+ GUniquePtr<GError> error;
+#ifdef __OpenBSD__
+ gboolean result = g_app_info_launch_uris_openbsd(
+ aInfo, spec.get(), GetLaunchContext(aXDGToken).get(),
+ getter_Transfers(error));
+#else
+ gboolean result = g_app_info_launch_uris(
+ aInfo, &uris, GetLaunchContext(aXDGToken).get(), getter_Transfers(error));
+#endif
+ if (!result) {
+ g_warning("Cannot launch application: %s", error->message);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::LaunchWithURI(nsIURI* aUri,
+ mozilla::dom::BrowsingContext* aBrowsingContext) {
+ auto promise = mozilla::widget::RequestWaylandFocusPromise();
+ if (!promise) {
+ return LaunchWithURIImpl(mApp, aUri);
+ }
+ promise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ /* resolve */
+ [app = RefPtr{mApp}, uri = RefPtr{aUri}](nsCString token) {
+ LaunchWithURIImpl(app, uri, token.get());
+ },
+ /* reject */
+ [app = RefPtr{mApp}, uri = RefPtr{aUri}](bool state) {
+ LaunchWithURIImpl(app, uri);
+ });
+ return NS_OK;
+}
+
+class GIOUTF8StringEnumerator final : public nsStringEnumeratorBase {
+ ~GIOUTF8StringEnumerator() = default;
+
+ public:
+ GIOUTF8StringEnumerator() : mIndex(0) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+
+ using nsStringEnumeratorBase::GetNext;
+
+ nsTArray<nsCString> mStrings;
+ uint32_t mIndex;
+};
+
+NS_IMPL_ISUPPORTS(GIOUTF8StringEnumerator, nsIUTF8StringEnumerator,
+ nsIStringEnumerator)
+
+NS_IMETHODIMP
+GIOUTF8StringEnumerator::HasMore(bool* aResult) {
+ *aResult = mIndex < mStrings.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOUTF8StringEnumerator::GetNext(nsACString& aResult) {
+ if (mIndex >= mStrings.Length()) return NS_ERROR_UNEXPECTED;
+
+ aResult.Assign(mStrings[mIndex]);
+ ++mIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetSupportedURISchemes(nsIUTF8StringEnumerator** aSchemes) {
+ *aSchemes = nullptr;
+
+ RefPtr<GIOUTF8StringEnumerator> array = new GIOUTF8StringEnumerator();
+
+ GVfs* gvfs = g_vfs_get_default();
+
+ if (!gvfs) {
+ g_warning("Cannot get GVfs object.");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ const gchar* const* uri_schemes = g_vfs_get_supported_uri_schemes(gvfs);
+
+ while (*uri_schemes != nullptr) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ array->mStrings.AppendElement(*uri_schemes);
+ uri_schemes++;
+ }
+
+ array.forget(aSchemes);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::SetAsDefaultForMimeType(nsACString const& aMimeType) {
+ GUniquePtr<char> content_type(
+ g_content_type_from_mime_type(PromiseFlatCString(aMimeType).get()));
+ if (!content_type) return NS_ERROR_FAILURE;
+ GUniquePtr<GError> error;
+ g_app_info_set_as_default_for_type(mApp, content_type.get(),
+ getter_Transfers(error));
+ if (error) {
+ g_warning("Cannot set application as default for MIME type (%s): %s",
+ PromiseFlatCString(aMimeType).get(), error->message);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+/**
+ * Set default application for files with given extensions
+ * @param fileExts string of space separated extensions
+ * @return NS_OK when application was set as default for given extensions,
+ * NS_ERROR_FAILURE otherwise
+ */
+NS_IMETHODIMP
+nsGIOMimeApp::SetAsDefaultForFileExtensions(nsACString const& fileExts) {
+ GUniquePtr<GError> error;
+ GUniquePtr<char> extensions(g_strdup(PromiseFlatCString(fileExts).get()));
+ char* ext_pos = extensions.get();
+ char* space_pos;
+
+ while ((space_pos = strchr(ext_pos, ' ')) || (*ext_pos != '\0')) {
+ if (space_pos) {
+ *space_pos = '\0';
+ }
+ g_app_info_set_as_default_for_extension(mApp, ext_pos,
+ getter_Transfers(error));
+ if (error) {
+ g_warning("Cannot set application as default for extension (%s): %s",
+ ext_pos, error->message);
+ return NS_ERROR_FAILURE;
+ }
+ if (space_pos) {
+ ext_pos = space_pos + 1;
+ } else {
+ *ext_pos = '\0';
+ }
+ }
+ return NS_OK;
+}
+
+/**
+ * Set default application for URI's of a particular scheme
+ * @param aURIScheme string containing the URI scheme
+ * @return NS_OK when application was set as default for URI scheme,
+ * NS_ERROR_FAILURE otherwise
+ */
+NS_IMETHODIMP
+nsGIOMimeApp::SetAsDefaultForURIScheme(nsACString const& aURIScheme) {
+ GUniquePtr<GError> error;
+ nsAutoCString contentType("x-scheme-handler/");
+ contentType.Append(aURIScheme);
+
+ g_app_info_set_as_default_for_type(mApp, contentType.get(),
+ getter_Transfers(error));
+ if (error) {
+ g_warning("Cannot set application as default for URI scheme (%s): %s",
+ PromiseFlatCString(aURIScheme).get(), error->message);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsGIOService, nsIGIOService)
+
+NS_IMETHODIMP
+nsGIOService::GetMimeTypeFromExtension(const nsACString& aExtension,
+ nsACString& aMimeType) {
+ nsAutoCString fileExtToUse("file.");
+ fileExtToUse.Append(aExtension);
+
+ gboolean result_uncertain;
+ GUniquePtr<char> content_type(
+ g_content_type_guess(fileExtToUse.get(), nullptr, 0, &result_uncertain));
+ if (!content_type) {
+ return NS_ERROR_FAILURE;
+ }
+
+ GUniquePtr<char> mime_type(g_content_type_get_mime_type(content_type.get()));
+ if (!mime_type) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aMimeType.Assign(mime_type.get());
+ return NS_OK;
+}
+// used in nsGNOMERegistry
+// -----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsGIOService::GetAppForURIScheme(const nsACString& aURIScheme,
+ nsIHandlerApp** aApp) {
+ *aApp = nullptr;
+
+ // Application in flatpak sandbox does not have access to the list
+ // of installed applications on the system. We use generic
+ // nsFlatpakHandlerApp which forwards launch call to the system.
+ if (widget::ShouldUsePortal(widget::PortalKind::MimeHandler)) {
+ if (mozilla::net::IsLoopbackHostname(aURIScheme)) {
+ // When the user writes foo:1234, we try to handle it natively using
+ // GetAppForURIScheme, and if that fails, we carry on. On flatpak there's
+ // no way to know if an app has handlers or not. Some things like
+ // localhost:1234 are really unlikely to be handled by native
+ // apps, and we're much better off returning an error here instead.
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<nsFlatpakHandlerApp> mozApp = new nsFlatpakHandlerApp();
+ mozApp.forget(aApp);
+ return NS_OK;
+ }
+
+ RefPtr<GAppInfo> app_info = dont_AddRef(g_app_info_get_default_for_uri_scheme(
+ PromiseFlatCString(aURIScheme).get()));
+ if (!app_info) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<nsGIOMimeApp> mozApp = new nsGIOMimeApp(app_info.forget());
+ mozApp.forget(aApp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOService::GetAppsForURIScheme(const nsACString& aURIScheme,
+ nsIMutableArray** aResult) {
+ // We don't need to return the nsFlatpakHandlerApp here because
+ // it would be skipped by the callers anyway.
+ // The preferred handler is provided by GetAppForURIScheme.
+ // This method returns all possible application handlers
+ // including preferred one. The callers skips the preferred
+ // handler in this list to avoid duplicate records in the list
+ // they create.
+ nsCOMPtr<nsIMutableArray> handlersArray =
+ do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+ nsAutoCString contentType("x-scheme-handler/");
+ contentType.Append(aURIScheme);
+
+ GList* appInfoList = g_app_info_get_all_for_type(contentType.get());
+ // g_app_info_get_all_for_type returns NULL when no appinfo is found
+ // or error occurs (contentType is NULL). We are fine with empty app list
+ // and we're sure that contentType is not NULL, so we won't return failure.
+ if (appInfoList) {
+ GList* appInfo = appInfoList;
+ while (appInfo) {
+ nsCOMPtr<nsIGIOMimeApp> mimeApp =
+ new nsGIOMimeApp(dont_AddRef(G_APP_INFO(appInfo->data)));
+ handlersArray->AppendElement(mimeApp);
+ appInfo = appInfo->next;
+ }
+ g_list_free(appInfoList);
+ }
+ handlersArray.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOService::GetAppForMimeType(const nsACString& aMimeType,
+ nsIHandlerApp** aApp) {
+ *aApp = nullptr;
+
+ // Flatpak does not reveal installed application to the sandbox,
+ // we need to create generic system handler.
+ if (widget::ShouldUsePortal(widget::PortalKind::MimeHandler)) {
+ RefPtr<nsFlatpakHandlerApp> mozApp = new nsFlatpakHandlerApp();
+ mozApp.forget(aApp);
+ return NS_OK;
+ }
+
+ GUniquePtr<char> content_type(
+ g_content_type_from_mime_type(PromiseFlatCString(aMimeType).get()));
+ if (!content_type) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // GIO returns "unknown" appinfo for the application/octet-stream, which is
+ // useless. It's better to fallback to create appinfo from file extension
+ // later.
+ if (g_content_type_is_unknown(content_type.get())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<GAppInfo> app_info =
+ dont_AddRef(g_app_info_get_default_for_type(content_type.get(), false));
+ if (!app_info) {
+ return NS_ERROR_FAILURE;
+ }
+#ifdef __OpenBSD__
+ char* t;
+ t = g_find_program_in_path(g_app_info_get_executable(app_info));
+ if (t != nullptr) {
+ g_debug("%s is registered as handler for %s, binary available as %s",
+ g_app_info_get_executable(app_info), content_type.get(), t);
+ } else {
+ g_warning(
+ "%s is registered as handler for %s but not available in PATH "
+ "(missing unveil ?)",
+ g_app_info_get_executable(app_info), content_type.get());
+ }
+#endif
+ RefPtr<nsGIOMimeApp> mozApp = new nsGIOMimeApp(app_info.forget());
+ mozApp.forget(aApp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOService::GetDescriptionForMimeType(const nsACString& aMimeType,
+ nsACString& aDescription) {
+ GUniquePtr<char> content_type(
+ g_content_type_from_mime_type(PromiseFlatCString(aMimeType).get()));
+ if (!content_type) {
+ return NS_ERROR_FAILURE;
+ }
+
+ GUniquePtr<char> desc(g_content_type_get_description(content_type.get()));
+ if (!desc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aDescription.Assign(desc.get());
+ return NS_OK;
+}
+
+static nsresult ShowURIImpl(nsIURI* aURI, const char* aXDGToken = nullptr) {
+ nsAutoCString spec;
+ MOZ_TRY(aURI->GetSpec(spec));
+ GUniquePtr<GError> error;
+#ifdef __OpenBSD__
+ if (!g_app_info_launch_default_for_uri_openbsd(
+ spec.get(), GetLaunchContext(aXDGToken).get(),
+#else
+ if (!g_app_info_launch_default_for_uri(spec.get(),
+ GetLaunchContext(aXDGToken).get(),
+#endif
+ getter_Transfers(error))) {
+ g_warning("Could not launch default application for URI: %s",
+ error->message);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult nsGIOService::ShowURI(nsIURI* aURI) {
+ auto promise = mozilla::widget::RequestWaylandFocusPromise();
+ if (!promise) {
+ return ShowURIImpl(aURI);
+ }
+ promise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ /* resolve */
+ [uri = RefPtr{aURI}](nsCString token) { ShowURIImpl(uri, token.get()); },
+ /* reject */
+ [uri = RefPtr{aURI}](bool state) { ShowURIImpl(uri); });
+ return NS_OK;
+}
+
+static nsresult LaunchPathImpl(const nsACString& aPath,
+ const char* aXDGToken = nullptr) {
+ RefPtr<GFile> file = dont_AddRef(
+ g_file_new_for_commandline_arg(PromiseFlatCString(aPath).get()));
+ GUniquePtr<char> spec(g_file_get_uri(file));
+ GUniquePtr<GError> error;
+#ifdef __OpenBSD__
+ g_app_info_launch_default_for_uri_openbsd(spec.get(),
+ GetLaunchContext(aXDGToken).get(),
+#else
+ g_app_info_launch_default_for_uri(spec.get(),
+ GetLaunchContext(aXDGToken).get(),
+#endif
+ getter_Transfers(error));
+ if (error) {
+ g_warning("Cannot launch default application: %s", error->message);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+static nsresult LaunchPath(const nsACString& aPath) {
+ auto promise = mozilla::widget::RequestWaylandFocusPromise();
+ if (!promise) {
+ return LaunchPathImpl(aPath);
+ }
+ promise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ /* resolve */
+ [path = nsCString{aPath}](nsCString token) {
+ LaunchPathImpl(path, token.get());
+ },
+ /* reject */
+ [path = nsCString{aPath}](bool state) { LaunchPathImpl(path); });
+ return NS_OK;
+}
+
+nsresult nsGIOService::LaunchFile(const nsACString& aPath) {
+ return LaunchPath(aPath);
+}
+
+nsresult nsGIOService::GetIsRunningUnderFlatpak(bool* aResult) {
+ *aResult = mozilla::widget::IsRunningUnderFlatpak();
+ return NS_OK;
+}
+
+nsresult nsGIOService::GetIsRunningUnderSnap(bool* aResult) {
+ *aResult = mozilla::widget::IsRunningUnderSnap();
+ return NS_OK;
+}
+
+static nsresult RevealDirectory(nsIFile* aFile, bool aForce) {
+ nsAutoCString path;
+ if (bool isDir; NS_SUCCEEDED(aFile->IsDirectory(&isDir)) && isDir) {
+ MOZ_TRY(aFile->GetNativePath(path));
+ return LaunchPath(path);
+ }
+
+ if (!aForce) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> parentDir;
+ MOZ_TRY(aFile->GetParent(getter_AddRefs(parentDir)));
+ MOZ_TRY(parentDir->GetNativePath(path));
+ return LaunchPath(path);
+}
+
+#ifdef MOZ_ENABLE_DBUS
+// Classic DBus
+const char kFreedesktopFileManagerName[] = "org.freedesktop.FileManager1";
+const char kFreedesktopFileManagerPath[] = "/org/freedesktop/FileManager1";
+const char kMethodShowItems[] = "ShowItems";
+
+// Portal for Snap, Flatpak
+const char kFreedesktopPortalName[] = "org.freedesktop.portal.Desktop";
+const char kFreedesktopPortalPath[] = "/org/freedesktop/portal/desktop";
+const char kFreedesktopPortalOpenURI[] = "org.freedesktop.portal.OpenURI";
+const char kMethodOpenDirectory[] = "OpenDirectory";
+
+static nsresult RevealFileViaDBusWithProxy(GDBusProxy* aProxy, nsIFile* aFile,
+ const char* aMethod) {
+ nsAutoCString path;
+ MOZ_TRY(aFile->GetNativePath(path));
+
+ RefPtr<mozilla::widget::DBusCallPromise> dbusPromise;
+ const char* startupId = "";
+
+ const int32_t timeout =
+ StaticPrefs::widget_gtk_file_manager_show_items_timeout_ms();
+
+ if (!(strcmp(aMethod, kMethodOpenDirectory) == 0)) {
+ GUniquePtr<gchar> uri(g_filename_to_uri(path.get(), nullptr, nullptr));
+ if (!uri) {
+ RevealDirectory(aFile, /* aForce = */ true);
+ return NS_ERROR_FAILURE;
+ }
+
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_STRING_ARRAY);
+ g_variant_builder_add(&builder, "s", uri.get());
+
+ RefPtr<GVariant> variant = dont_AddRef(
+ g_variant_ref_sink(g_variant_new("(ass)", &builder, startupId)));
+ g_variant_builder_clear(&builder);
+
+ dbusPromise = widget::DBusProxyCall(aProxy, aMethod, variant,
+ G_DBUS_CALL_FLAGS_NONE, timeout);
+ } else {
+ int fd = open(path.get(), O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ g_printerr("Failed to open file: %s returned %d\n", path.get(), errno);
+ RevealDirectory(aFile, /* aForce = */ true);
+ return NS_ERROR_FAILURE;
+ }
+
+ GVariantBuilder options;
+ g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
+
+ static auto g_unix_fd_list_new_from_array =
+ (GUnixFDList * (*)(const gint* fds, gint n_fds))
+ dlsym(RTLD_DEFAULT, "g_unix_fd_list_new_from_array");
+
+ // Will take ownership of the fd, so we dont have to care about it anymore
+ RefPtr<GUnixFDList> fd_list =
+ dont_AddRef(g_unix_fd_list_new_from_array(&fd, 1));
+
+ RefPtr<GVariant> variant = dont_AddRef(
+ g_variant_ref_sink(g_variant_new("(sha{sv})", startupId, 0, &options)));
+ g_variant_builder_clear(&options);
+
+ dbusPromise = widget::DBusProxyCallWithUnixFDList(
+ aProxy, aMethod, variant, G_DBUS_CALL_FLAGS_NONE, timeout, fd_list);
+ }
+
+ dbusPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [](RefPtr<GVariant>&& aResult) {
+ // Do nothing, file is shown, we're done.
+ },
+ [file = RefPtr{aFile}, aMethod](GUniquePtr<GError>&& aError) {
+ g_printerr("Failed to query file manager via %s: %s\n", aMethod,
+ aError->message);
+ RevealDirectory(file, /* aForce = */ true);
+ });
+ return NS_OK;
+}
+
+static void RevealFileViaDBus(nsIFile* aFile, const char* aName,
+ const char* aPath, const char* aCall,
+ const char* aMethod) {
+ widget::CreateDBusProxyForBus(
+ G_BUS_TYPE_SESSION,
+ GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES),
+ /* aInterfaceInfo = */ nullptr, aName, aPath, aCall)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [file = RefPtr{aFile}, aMethod](RefPtr<GDBusProxy>&& aProxy) {
+ RevealFileViaDBusWithProxy(aProxy.get(), file, aMethod);
+ },
+ [file = RefPtr{aFile}, aName](GUniquePtr<GError>&& aError) {
+ g_printerr("Failed to create DBUS proxy for %s: %s\n", aName,
+ aError->message);
+ RevealDirectory(file, /* aForce = */ true);
+ });
+}
+
+static void RevealFileViaDBusClassic(nsIFile* aFile) {
+ RevealFileViaDBus(aFile, kFreedesktopFileManagerName,
+ kFreedesktopFileManagerPath, kFreedesktopFileManagerName,
+ kMethodShowItems);
+}
+
+static void RevealFileViaDBusPortal(nsIFile* aFile) {
+ RevealFileViaDBus(aFile, kFreedesktopPortalName, kFreedesktopPortalPath,
+ kFreedesktopPortalOpenURI, kMethodOpenDirectory);
+}
+#endif
+
+nsresult nsGIOService::RevealFile(nsIFile* aFile) {
+#ifdef MOZ_ENABLE_DBUS
+ if (NS_SUCCEEDED(RevealDirectory(aFile, /* aForce = */ false))) {
+ return NS_OK;
+ }
+ if (ShouldUsePortal(widget::PortalKind::OpenUri)) {
+ RevealFileViaDBusPortal(aFile);
+ } else {
+ RevealFileViaDBusClassic(aFile);
+ }
+ return NS_OK;
+#else
+ return RevealDirectory(aFile, /* aForce = */ true);
+#endif
+}
+
+/**
+ * Find GIO Mime App from given commandline.
+ * This is different from CreateAppFromCommand because instead of creating the
+ * GIO Mime App in case it's not found in the GIO application list, the method
+ * returns error.
+ * @param aCmd command with parameters used to start the application
+ * @return NS_OK when application is found, NS_ERROR_NOT_AVAILABLE otherwise
+ */
+NS_IMETHODIMP
+nsGIOService::FindAppFromCommand(nsACString const& aCmd,
+ nsIGIOMimeApp** aAppInfo) {
+ RefPtr<GAppInfo> app_info;
+
+ GList* apps = g_app_info_get_all();
+
+ // Try to find relevant and existing GAppInfo in all installed application
+ // We do this by comparing each GAppInfo's executable with out own
+ for (GList* node = apps; node; node = node->next) {
+ RefPtr<GAppInfo> app_info_from_list = dont_AddRef((GAppInfo*)node->data);
+ node->data = nullptr;
+ if (!app_info) {
+ // If the executable is not absolute, get it's full path
+ GUniquePtr<char> executable(g_find_program_in_path(
+ g_app_info_get_executable(app_info_from_list)));
+
+ if (executable &&
+ strcmp(executable.get(), PromiseFlatCString(aCmd).get()) == 0) {
+ app_info = std::move(app_info_from_list);
+ // Can't break here because we need to keep iterating to unref the other
+ // nodes.
+ }
+ }
+ }
+
+ g_list_free(apps);
+ if (!app_info) {
+ *aAppInfo = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ RefPtr<nsGIOMimeApp> app = new nsGIOMimeApp(app_info.forget());
+ app.forget(aAppInfo);
+ return NS_OK;
+}
+
+/**
+ * Create application info for specified command and application name.
+ * Command arguments are ignored and the "%u" is always added.
+ * @param cmd command to execute
+ * @param appName application name
+ * @param appInfo location where created GAppInfo is stored
+ * @return NS_OK when object is created, NS_ERROR_FILE_NOT_FOUND when executable
+ * is not found in the system path or NS_ERROR_FAILURE otherwise.
+ */
+NS_IMETHODIMP
+nsGIOService::CreateAppFromCommand(nsACString const& cmd,
+ nsACString const& appName,
+ nsIGIOMimeApp** appInfo) {
+ *appInfo = nullptr;
+
+ // Using G_APP_INFO_CREATE_SUPPORTS_URIS calling
+ // g_app_info_create_from_commandline appends %u to the cmd even when cmd
+ // already contains this parameter. To avoid that we're going to remove
+ // arguments before passing to it.
+ nsAutoCString commandWithoutArgs;
+ nsresult rv = GetCommandFromCommandline(cmd, commandWithoutArgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ GUniquePtr<GError> error;
+ RefPtr<GAppInfo> app_info = dont_AddRef(g_app_info_create_from_commandline(
+ commandWithoutArgs.BeginReading(), PromiseFlatCString(appName).get(),
+ G_APP_INFO_CREATE_SUPPORTS_URIS, getter_Transfers(error)));
+ if (!app_info) {
+ g_warning("Cannot create application info from command: %s",
+ error->message);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Check if executable exist in path
+ GUniquePtr<gchar> executableWithFullPath(
+ g_find_program_in_path(commandWithoutArgs.BeginReading()));
+ if (!executableWithFullPath) {
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+ RefPtr<nsGIOMimeApp> mozApp = new nsGIOMimeApp(app_info.forget());
+ mozApp.forget(appInfo);
+ return NS_OK;
+}
diff --git a/toolkit/system/gnome/nsGIOService.h b/toolkit/system/gnome/nsGIOService.h
new file mode 100644
index 0000000000..e83c724de2
--- /dev/null
+++ b/toolkit/system/gnome/nsGIOService.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGIOService_h_
+#define nsGIOService_h_
+
+#include "nsIGIOService.h"
+
+#define NS_GIOSERVICE_CID \
+ { \
+ 0xe3a1f3c9, 0x3ae1, 0x4b40, { \
+ 0xa5, 0xe0, 0x7b, 0x45, 0x7f, 0xc9, 0xa9, 0xad \
+ } \
+ }
+
+class nsGIOService final : public nsIGIOService {
+ ~nsGIOService() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGIOSERVICE
+};
+
+#endif
diff --git a/toolkit/system/gnome/nsGSettingsService.cpp b/toolkit/system/gnome/nsGSettingsService.cpp
new file mode 100644
index 0000000000..73cba5c947
--- /dev/null
+++ b/toolkit/system/gnome/nsGSettingsService.cpp
@@ -0,0 +1,316 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsGSettingsService.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "prlink.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMutableArray.h"
+#include "nsISupportsPrimitives.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+using namespace mozilla;
+
+typedef struct _GSettings GSettings;
+typedef struct _GVariantType GVariantType;
+typedef struct _GVariant GVariant;
+
+#ifndef G_VARIANT_TYPE_INT32
+# define G_VARIANT_TYPE_INT32 ((const GVariantType*)"i")
+# define G_VARIANT_TYPE_BOOLEAN ((const GVariantType*)"b")
+# define G_VARIANT_TYPE_STRING ((const GVariantType*)"s")
+# define G_VARIANT_TYPE_OBJECT_PATH ((const GVariantType*)"o")
+# define G_VARIANT_TYPE_SIGNATURE ((const GVariantType*)"g")
+#endif
+#ifndef G_VARIANT_TYPE_STRING_ARRAY
+# define G_VARIANT_TYPE_STRING_ARRAY ((const GVariantType*)"as")
+#endif
+
+#define GSETTINGS_FUNCTIONS \
+ FUNC(g_settings_new, GSettings*, (const char* schema)) \
+ FUNC(g_settings_list_schemas, const char* const*, (void)) \
+ FUNC(g_settings_list_keys, char**, (GSettings * settings)) \
+ FUNC(g_settings_get_value, GVariant*, \
+ (GSettings * settings, const char* key)) \
+ FUNC(g_settings_set_value, gboolean, \
+ (GSettings * settings, const char* key, GVariant* value)) \
+ FUNC(g_settings_range_check, gboolean, \
+ (GSettings * settings, const char* key, GVariant* value)) \
+ FUNC(g_variant_get_int32, gint32, (GVariant * variant)) \
+ FUNC(g_variant_get_boolean, gboolean, (GVariant * variant)) \
+ FUNC(g_variant_get_string, const char*, (GVariant * value, gsize * length)) \
+ FUNC(g_variant_get_strv, const char**, (GVariant * value, gsize * length)) \
+ FUNC(g_variant_is_of_type, gboolean, \
+ (GVariant * value, const GVariantType* type)) \
+ FUNC(g_variant_new_int32, GVariant*, (gint32 value)) \
+ FUNC(g_variant_new_boolean, GVariant*, (gboolean value)) \
+ FUNC(g_variant_new_string, GVariant*, (const char* string)) \
+ FUNC(g_variant_unref, void, (GVariant * value))
+
+#define FUNC(name, type, params) \
+ typedef type(*_##name##_fn) params; \
+ static _##name##_fn _##name;
+
+GSETTINGS_FUNCTIONS
+
+#undef FUNC
+
+#define g_settings_new _g_settings_new
+#define g_settings_list_schemas _g_settings_list_schemas
+#define g_settings_list_keys _g_settings_list_keys
+#define g_settings_get_value _g_settings_get_value
+#define g_settings_set_value _g_settings_set_value
+#define g_settings_range_check _g_settings_range_check
+#define g_variant_get_int32 _g_variant_get_int32
+#define g_variant_get_boolean _g_variant_get_boolean
+#define g_variant_get_string _g_variant_get_string
+#define g_variant_get_strv _g_variant_get_strv
+#define g_variant_is_of_type _g_variant_is_of_type
+#define g_variant_new_int32 _g_variant_new_int32
+#define g_variant_new_boolean _g_variant_new_boolean
+#define g_variant_new_string _g_variant_new_string
+#define g_variant_unref _g_variant_unref
+
+static PRLibrary* gioLib = nullptr;
+
+class nsGSettingsCollection final : public nsIGSettingsCollection {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGSETTINGSCOLLECTION
+
+ explicit nsGSettingsCollection(GSettings* aSettings)
+ : mSettings(aSettings), mKeys(nullptr) {}
+
+ private:
+ ~nsGSettingsCollection();
+
+ bool KeyExists(const nsACString& aKey);
+ bool SetValue(const nsACString& aKey, GVariant* aValue);
+
+ GSettings* mSettings;
+ char** mKeys;
+};
+
+nsGSettingsCollection::~nsGSettingsCollection() {
+ g_strfreev(mKeys);
+ g_object_unref(mSettings);
+}
+
+bool nsGSettingsCollection::KeyExists(const nsACString& aKey) {
+ if (!mKeys) mKeys = g_settings_list_keys(mSettings);
+
+ for (uint32_t i = 0; mKeys[i] != nullptr; i++) {
+ if (aKey.Equals(mKeys[i])) return true;
+ }
+
+ return false;
+}
+
+bool nsGSettingsCollection::SetValue(const nsACString& aKey, GVariant* aValue) {
+ if (!KeyExists(aKey) ||
+ !g_settings_range_check(mSettings, PromiseFlatCString(aKey).get(),
+ aValue)) {
+ g_variant_unref(aValue);
+ return false;
+ }
+
+ return g_settings_set_value(mSettings, PromiseFlatCString(aKey).get(),
+ aValue);
+}
+
+NS_IMPL_ISUPPORTS(nsGSettingsCollection, nsIGSettingsCollection)
+
+NS_IMETHODIMP
+nsGSettingsCollection::SetString(const nsACString& aKey,
+ const nsACString& aValue) {
+ GVariant* value = g_variant_new_string(PromiseFlatCString(aValue).get());
+ if (!value) return NS_ERROR_OUT_OF_MEMORY;
+
+ bool res = SetValue(aKey, value);
+
+ return res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::SetBoolean(const nsACString& aKey, bool aValue) {
+ GVariant* value = g_variant_new_boolean(aValue);
+ if (!value) return NS_ERROR_OUT_OF_MEMORY;
+
+ bool res = SetValue(aKey, value);
+
+ return res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::SetInt(const nsACString& aKey, int32_t aValue) {
+ GVariant* value = g_variant_new_int32(aValue);
+ if (!value) return NS_ERROR_OUT_OF_MEMORY;
+
+ bool res = SetValue(aKey, value);
+
+ return res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::GetString(const nsACString& aKey, nsACString& aResult) {
+ if (!KeyExists(aKey)) return NS_ERROR_INVALID_ARG;
+
+ GVariant* value =
+ g_settings_get_value(mSettings, PromiseFlatCString(aKey).get());
+ if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING) &&
+ !g_variant_is_of_type(value, G_VARIANT_TYPE_OBJECT_PATH) &&
+ !g_variant_is_of_type(value, G_VARIANT_TYPE_SIGNATURE)) {
+ g_variant_unref(value);
+ return NS_ERROR_FAILURE;
+ }
+
+ aResult.Assign(g_variant_get_string(value, nullptr));
+ g_variant_unref(value);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::GetBoolean(const nsACString& aKey, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!KeyExists(aKey)) return NS_ERROR_INVALID_ARG;
+
+ GVariant* value =
+ g_settings_get_value(mSettings, PromiseFlatCString(aKey).get());
+ if (!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) {
+ g_variant_unref(value);
+ return NS_ERROR_FAILURE;
+ }
+
+ gboolean res = g_variant_get_boolean(value);
+ *aResult = res ? true : false;
+ g_variant_unref(value);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::GetInt(const nsACString& aKey, int32_t* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!KeyExists(aKey)) return NS_ERROR_INVALID_ARG;
+
+ GVariant* value =
+ g_settings_get_value(mSettings, PromiseFlatCString(aKey).get());
+ if (!g_variant_is_of_type(value, G_VARIANT_TYPE_INT32)) {
+ g_variant_unref(value);
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = g_variant_get_int32(value);
+ g_variant_unref(value);
+
+ return NS_OK;
+}
+
+// These types are local to nsGSettingsService::Init, but ISO C++98 doesn't
+// allow a template (ArrayLength) to be instantiated based on a local type.
+// Boo-urns!
+typedef void (*nsGSettingsFunc)();
+struct nsGSettingsDynamicFunction {
+ const char* functionName;
+ nsGSettingsFunc* function;
+};
+
+NS_IMETHODIMP
+nsGSettingsCollection::GetStringList(const nsACString& aKey,
+ nsIArray** aResult) {
+ if (!KeyExists(aKey)) return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIMutableArray> items(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ if (!items) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ GVariant* value =
+ g_settings_get_value(mSettings, PromiseFlatCString(aKey).get());
+
+ if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING_ARRAY)) {
+ g_variant_unref(value);
+ return NS_ERROR_FAILURE;
+ }
+
+ const gchar** gs_strings = g_variant_get_strv(value, nullptr);
+ if (!gs_strings) {
+ // empty array
+ items.forget(aResult);
+ g_variant_unref(value);
+ return NS_OK;
+ }
+
+ const gchar** p_gs_strings = gs_strings;
+ while (*p_gs_strings != nullptr) {
+ nsCOMPtr<nsISupportsCString> obj(
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+ if (obj) {
+ obj->SetData(nsDependentCString(*p_gs_strings));
+ items->AppendElement(obj);
+ }
+ p_gs_strings++;
+ }
+ g_free(gs_strings);
+ items.forget(aResult);
+ g_variant_unref(value);
+ return NS_OK;
+}
+
+nsresult nsGSettingsService::Init() {
+#define FUNC(name, type, params) {#name, (nsGSettingsFunc*)&_##name},
+ static const nsGSettingsDynamicFunction kGSettingsSymbols[] = {
+ GSETTINGS_FUNCTIONS};
+#undef FUNC
+
+ if (!gioLib) {
+ gioLib = PR_LoadLibrary("libgio-2.0.so.0");
+ if (!gioLib) return NS_ERROR_FAILURE;
+ }
+
+ for (auto GSettingsSymbol : kGSettingsSymbols) {
+ *GSettingsSymbol.function =
+ PR_FindFunctionSymbol(gioLib, GSettingsSymbol.functionName);
+ if (!*GSettingsSymbol.function) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsGSettingsService, nsIGSettingsService)
+
+nsGSettingsService::~nsGSettingsService() {
+ if (gioLib) {
+ PR_UnloadLibrary(gioLib);
+ gioLib = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsGSettingsService::GetCollectionForSchema(
+ const nsACString& schema, nsIGSettingsCollection** collection) {
+ NS_ENSURE_ARG_POINTER(collection);
+
+ const char* const* schemas = g_settings_list_schemas();
+
+ for (uint32_t i = 0; schemas[i] != nullptr; i++) {
+ if (schema.Equals(schemas[i])) {
+ GSettings* settings = g_settings_new(PromiseFlatCString(schema).get());
+ nsGSettingsCollection* mozGSettings = new nsGSettingsCollection(settings);
+ NS_ADDREF(*collection = mozGSettings);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
diff --git a/toolkit/system/gnome/nsGSettingsService.h b/toolkit/system/gnome/nsGSettingsService.h
new file mode 100644
index 0000000000..1eb592e4aa
--- /dev/null
+++ b/toolkit/system/gnome/nsGSettingsService.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGSettingsService_h_
+#define nsGSettingsService_h_
+
+#include "nsIGSettingsService.h"
+
+#define NS_GSETTINGSSERVICE_CID \
+ { \
+ 0xbfd4a9d8, 0xd886, 0x4161, { \
+ 0x81, 0xef, 0x88, 0x68, 0xda, 0x11, 0x41, 0x70 \
+ } \
+ }
+
+class nsGSettingsService final : public nsIGSettingsService {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGSETTINGSSERVICE
+
+ nsresult Init();
+
+ private:
+ ~nsGSettingsService();
+};
+
+#endif
diff --git a/toolkit/system/gnome/nsSystemAlertsService.cpp b/toolkit/system/gnome/nsSystemAlertsService.cpp
new file mode 100644
index 0000000000..870eb029f7
--- /dev/null
+++ b/toolkit/system/gnome/nsSystemAlertsService.cpp
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsComponentManagerUtils.h"
+#include "nsSystemAlertsService.h"
+#include "nsAlertsIconListener.h"
+
+NS_IMPL_ADDREF(nsSystemAlertsService)
+NS_IMPL_RELEASE(nsSystemAlertsService)
+
+NS_INTERFACE_MAP_BEGIN(nsSystemAlertsService)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAlertsService)
+ NS_INTERFACE_MAP_ENTRY(nsIAlertsService)
+ NS_INTERFACE_MAP_ENTRY(nsIAlertsDoNotDisturb)
+NS_INTERFACE_MAP_END
+
+nsSystemAlertsService::nsSystemAlertsService() = default;
+
+nsSystemAlertsService::~nsSystemAlertsService() = default;
+
+nsresult nsSystemAlertsService::Init() { return NS_OK; }
+
+NS_IMETHODIMP nsSystemAlertsService::ShowAlertNotification(
+ const nsAString& aImageUrl, const nsAString& aAlertTitle,
+ const nsAString& aAlertText, bool aAlertTextClickable,
+ const nsAString& aAlertCookie, nsIObserver* aAlertListener,
+ const nsAString& aAlertName, const nsAString& aBidi, const nsAString& aLang,
+ const nsAString& aData, nsIPrincipal* aPrincipal, bool aInPrivateBrowsing,
+ bool aRequireInteraction) {
+ nsCOMPtr<nsIAlertNotification> alert =
+ do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
+ NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE);
+ // vibrate is unused for now
+ nsTArray<uint32_t> vibrate;
+ nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle, aAlertText,
+ aAlertTextClickable, aAlertCookie, aBidi, aLang,
+ aData, aPrincipal, aInPrivateBrowsing,
+ aRequireInteraction, false, vibrate);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ShowAlert(alert, aAlertListener);
+}
+
+NS_IMETHODIMP nsSystemAlertsService::ShowPersistentNotification(
+ const nsAString& aPersistentData, nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener) {
+ return ShowAlert(aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP nsSystemAlertsService::ShowAlert(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener) {
+ NS_ENSURE_ARG(aAlert);
+
+ nsAutoString alertName;
+ nsresult rv = aAlert->GetName(alertName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsAlertsIconListener> alertListener =
+ new nsAlertsIconListener(this, alertName);
+ if (!alertListener) return NS_ERROR_OUT_OF_MEMORY;
+
+ if (mSuppressForScreenSharing) {
+ alertListener->SendClosed();
+ return NS_OK;
+ }
+
+ AddListener(alertName, alertListener);
+ return alertListener->InitAlertAsync(aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP nsSystemAlertsService::CloseAlert(const nsAString& aAlertName,
+ bool aContextClosed) {
+ RefPtr<nsAlertsIconListener> listener = mActiveListeners.Get(aAlertName);
+ if (!listener) {
+ return NS_OK;
+ }
+ mActiveListeners.Remove(aAlertName);
+ return listener->Close();
+}
+
+NS_IMETHODIMP nsSystemAlertsService::GetManualDoNotDisturb(bool* aRetVal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsSystemAlertsService::SetManualDoNotDisturb(bool aDoNotDisturb) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsSystemAlertsService::GetSuppressForScreenSharing(
+ bool* aRetVal) {
+ NS_ENSURE_ARG(aRetVal);
+ *aRetVal = mSuppressForScreenSharing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSystemAlertsService::SetSuppressForScreenSharing(
+ bool aSuppress) {
+ mSuppressForScreenSharing = aSuppress;
+ return NS_OK;
+}
+
+bool nsSystemAlertsService::IsActiveListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener) {
+ return mActiveListeners.Get(aAlertName) == aListener;
+}
+
+void nsSystemAlertsService::AddListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener) {
+ const auto oldListener =
+ mActiveListeners.WithEntryHandle(aAlertName, [&](auto&& entry) {
+ RefPtr<nsAlertsIconListener> oldListener =
+ entry ? entry.Data() : nullptr;
+ entry.InsertOrUpdate(aListener);
+ return oldListener;
+ });
+ if (oldListener) {
+ // If an alert with this name already exists, close it.
+ oldListener->Close();
+ }
+}
+
+void nsSystemAlertsService::RemoveListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener) {
+ auto entry = mActiveListeners.Lookup(aAlertName);
+ if (entry && entry.Data() == aListener) {
+ // The alert may have been replaced; only remove it from the active
+ // listeners map if it's the same.
+ entry.Remove();
+ }
+}
diff --git a/toolkit/system/gnome/nsSystemAlertsService.h b/toolkit/system/gnome/nsSystemAlertsService.h
new file mode 100644
index 0000000000..d24168e9af
--- /dev/null
+++ b/toolkit/system/gnome/nsSystemAlertsService.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSystemAlertsService_h__
+#define nsSystemAlertsService_h__
+
+#include "nsIAlertsService.h"
+#include "nsTHashMap.h"
+
+class nsAlertsIconListener;
+
+class nsSystemAlertsService : public nsIAlertsService,
+ public nsIAlertsDoNotDisturb {
+ public:
+ NS_DECL_NSIALERTSSERVICE
+ NS_DECL_NSIALERTSDONOTDISTURB
+ NS_DECL_ISUPPORTS
+
+ nsSystemAlertsService();
+
+ nsresult Init();
+
+ bool IsActiveListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener);
+ void RemoveListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener);
+
+ protected:
+ virtual ~nsSystemAlertsService();
+
+ void AddListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener);
+
+ nsTHashMap<nsStringHashKey, nsAlertsIconListener*> mActiveListeners;
+
+ private:
+ bool mSuppressForScreenSharing = false;
+};
+
+#endif /* nsSystemAlertsService_h__ */
diff --git a/toolkit/system/osxproxy/components.conf b/toolkit/system/osxproxy/components.conf
new file mode 100644
index 0000000000..b3e3c464b6
--- /dev/null
+++ b/toolkit/system/osxproxy/components.conf
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{9afcd4b8-2e0f-41f4-8f1f-3bf0d3cf67de}',
+ 'contract_ids': ['@mozilla.org/system-proxy-settings;1'],
+ 'type': 'nsOSXSystemProxySettings',
+ },
+]
diff --git a/toolkit/system/osxproxy/moz.build b/toolkit/system/osxproxy/moz.build
new file mode 100644
index 0000000000..a28c9ce35c
--- /dev/null
+++ b/toolkit/system/osxproxy/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking")
+
+SOURCES += [
+ "nsOSXSystemProxySettings.mm",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "../commonproxy",
+ "/netwerk/base",
+]
diff --git a/toolkit/system/osxproxy/nsOSXSystemProxySettings.mm b/toolkit/system/osxproxy/nsOSXSystemProxySettings.mm
new file mode 100644
index 0000000000..b1ad41ccc1
--- /dev/null
+++ b/toolkit/system/osxproxy/nsOSXSystemProxySettings.mm
@@ -0,0 +1,428 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#import <Cocoa/Cocoa.h>
+#import <SystemConfiguration/SystemConfiguration.h>
+
+#include "nsISystemProxySettings.h"
+#include "mozilla/Components.h"
+#include "nsPrintfCString.h"
+#include "nsNetCID.h"
+#include "nsObjCExceptions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "ProxyUtils.h"
+#include "ProxyConfig.h"
+
+class nsOSXSystemProxySettings : public nsISystemProxySettings {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISYSTEMPROXYSETTINGS
+
+ nsOSXSystemProxySettings();
+ nsresult Init();
+
+ // called by OSX when the proxy settings have changed
+ void ProxyHasChanged();
+
+ // is there a PAC url specified in the system configuration
+ bool IsAutoconfigEnabled() const;
+ // retrieve the pac url
+ nsresult GetAutoconfigURL(nsAutoCString& aResult) const;
+
+ // Find the SystemConfiguration proxy & port for a given URI
+ nsresult FindSCProxyPort(const nsACString& aScheme, nsACString& aResultHost,
+ int32_t& aResultPort, bool& aResultSocksProxy);
+
+ // is host:port on the proxy exception list?
+ bool IsInExceptionList(const nsACString& aHost) const;
+
+ protected:
+ virtual ~nsOSXSystemProxySettings();
+ virtual void InitDone() {}
+ virtual void OnProxyConfigChangedInternal() {}
+
+ SCDynamicStoreContext mContext;
+ SCDynamicStoreRef mSystemDynamicStore;
+ NSDictionary* mProxyDict;
+
+ // Mapping of URI schemes to SystemConfiguration keys
+ struct SchemeMapping {
+ const char* mScheme;
+ CFStringRef mEnabled;
+ CFStringRef mHost;
+ CFStringRef mPort;
+ bool mIsSocksProxy;
+ };
+ static const SchemeMapping gSchemeMappingList[];
+};
+
+NS_IMPL_ISUPPORTS(nsOSXSystemProxySettings, nsISystemProxySettings)
+
+NS_IMETHODIMP
+nsOSXSystemProxySettings::GetMainThreadOnly(bool* aMainThreadOnly) {
+ *aMainThreadOnly = true;
+ return NS_OK;
+}
+
+// Mapping of URI schemes to SystemConfiguration keys
+const nsOSXSystemProxySettings::SchemeMapping
+ nsOSXSystemProxySettings::gSchemeMappingList[] = {
+ {"http", kSCPropNetProxiesHTTPEnable, kSCPropNetProxiesHTTPProxy,
+ kSCPropNetProxiesHTTPPort, false},
+ {"https", kSCPropNetProxiesHTTPSEnable, kSCPropNetProxiesHTTPSProxy,
+ kSCPropNetProxiesHTTPSPort, false},
+ {"ftp", kSCPropNetProxiesFTPEnable, kSCPropNetProxiesFTPProxy,
+ kSCPropNetProxiesFTPPort, false},
+ {"socks", kSCPropNetProxiesSOCKSEnable, kSCPropNetProxiesSOCKSProxy,
+ kSCPropNetProxiesSOCKSPort, true},
+ {NULL, NULL, NULL, NULL, false},
+};
+
+static void ProxyHasChangedWrapper(SCDynamicStoreRef aStore,
+ CFArrayRef aChangedKeys, void* aInfo) {
+ static_cast<nsOSXSystemProxySettings*>(aInfo)->ProxyHasChanged();
+}
+
+nsOSXSystemProxySettings::nsOSXSystemProxySettings()
+ : mSystemDynamicStore(NULL), mProxyDict(NULL) {
+ mContext = (SCDynamicStoreContext){0, this, NULL, NULL, NULL};
+}
+
+nsresult nsOSXSystemProxySettings::Init() {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ // Register for notification of proxy setting changes
+ // See:
+ // http://developer.apple.com/documentation/Networking/Conceptual/CFNetwork/CFStreamTasks/chapter_4_section_5.html
+ mSystemDynamicStore = SCDynamicStoreCreate(NULL, CFSTR("Mozilla"),
+ ProxyHasChangedWrapper, &mContext);
+ if (!mSystemDynamicStore) return NS_ERROR_FAILURE;
+
+ // Set up the store to monitor any changes to the proxies
+ CFStringRef proxiesKey = SCDynamicStoreKeyCreateProxies(NULL);
+ if (!proxiesKey) return NS_ERROR_FAILURE;
+
+ CFArrayRef keyArray = CFArrayCreate(NULL, (const void**)(&proxiesKey), 1,
+ &kCFTypeArrayCallBacks);
+ CFRelease(proxiesKey);
+ if (!keyArray) return NS_ERROR_FAILURE;
+
+ SCDynamicStoreSetNotificationKeys(mSystemDynamicStore, keyArray, NULL);
+ CFRelease(keyArray);
+
+ // Add the dynamic store to the run loop
+ CFRunLoopSourceRef storeRLSource =
+ SCDynamicStoreCreateRunLoopSource(NULL, mSystemDynamicStore, 0);
+ if (!storeRLSource) return NS_ERROR_FAILURE;
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), storeRLSource,
+ kCFRunLoopCommonModes);
+ CFRelease(storeRLSource);
+
+ // Load the initial copy of proxy info
+ mProxyDict = (NSDictionary*)SCDynamicStoreCopyProxies(mSystemDynamicStore);
+ if (!mProxyDict) return NS_ERROR_FAILURE;
+
+ InitDone();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+nsOSXSystemProxySettings::~nsOSXSystemProxySettings() {
+ NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
+
+ [mProxyDict release];
+
+ if (mSystemDynamicStore) {
+ // Invalidate the dynamic store's run loop source
+ // to get the store out of the run loop
+ CFRunLoopSourceRef rls =
+ SCDynamicStoreCreateRunLoopSource(NULL, mSystemDynamicStore, 0);
+ if (rls) {
+ CFRunLoopSourceInvalidate(rls);
+ CFRelease(rls);
+ }
+ CFRelease(mSystemDynamicStore);
+ }
+
+ NS_OBJC_END_TRY_IGNORE_BLOCK;
+}
+
+void nsOSXSystemProxySettings::ProxyHasChanged() {
+ NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
+
+ [mProxyDict release];
+ mProxyDict = (NSDictionary*)SCDynamicStoreCopyProxies(mSystemDynamicStore);
+
+ NS_OBJC_END_TRY_IGNORE_BLOCK;
+
+ OnProxyConfigChangedInternal();
+}
+
+nsresult nsOSXSystemProxySettings::FindSCProxyPort(const nsACString& aScheme,
+ nsACString& aResultHost,
+ int32_t& aResultPort,
+ bool& aResultSocksProxy) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ NS_ENSURE_TRUE(mProxyDict != NULL, NS_ERROR_FAILURE);
+
+ for (const SchemeMapping* keys = gSchemeMappingList; keys->mScheme != NULL;
+ ++keys) {
+ // Check for matching scheme (when appropriate)
+ if (strcasecmp(keys->mScheme, PromiseFlatCString(aScheme).get()) &&
+ !keys->mIsSocksProxy)
+ continue;
+
+ // Check the proxy is enabled
+ NSNumber* enabled = [mProxyDict objectForKey:(NSString*)keys->mEnabled];
+ NS_ENSURE_TRUE(enabled == NULL || [enabled isKindOfClass:[NSNumber class]],
+ NS_ERROR_FAILURE);
+ if ([enabled intValue] == 0) continue;
+
+ // Get the proxy host
+ NSString* host = [mProxyDict objectForKey:(NSString*)keys->mHost];
+ if (host == NULL) break;
+ NS_ENSURE_TRUE([host isKindOfClass:[NSString class]], NS_ERROR_FAILURE);
+ aResultHost.Assign([host UTF8String]);
+
+ // Get the proxy port
+ NSNumber* port = [mProxyDict objectForKey:(NSString*)keys->mPort];
+ NS_ENSURE_TRUE([port isKindOfClass:[NSNumber class]], NS_ERROR_FAILURE);
+ aResultPort = [port intValue];
+
+ aResultSocksProxy = keys->mIsSocksProxy;
+
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+bool nsOSXSystemProxySettings::IsAutoconfigEnabled() const {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ NSNumber* value = [mProxyDict
+ objectForKey:(NSString*)kSCPropNetProxiesProxyAutoConfigEnable];
+ NS_ENSURE_TRUE(value == NULL || [value isKindOfClass:[NSNumber class]],
+ false);
+ return ([value intValue] != 0);
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(false);
+}
+
+nsresult nsOSXSystemProxySettings::GetAutoconfigURL(
+ nsAutoCString& aResult) const {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ NSString* value = [mProxyDict
+ objectForKey:(NSString*)kSCPropNetProxiesProxyAutoConfigURLString];
+ if (value != NULL) {
+ NS_ENSURE_TRUE([value isKindOfClass:[NSString class]], NS_ERROR_FAILURE);
+ aResult.Assign([value UTF8String]);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+bool nsOSXSystemProxySettings::IsInExceptionList(
+ const nsACString& aHost) const {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ NS_ENSURE_TRUE(mProxyDict != NULL, false);
+
+ NSArray* exceptionList =
+ [mProxyDict objectForKey:(NSString*)kSCPropNetProxiesExceptionsList];
+ NS_ENSURE_TRUE(
+ exceptionList == NULL || [exceptionList isKindOfClass:[NSArray class]],
+ false);
+
+ NSEnumerator* exceptionEnumerator = [exceptionList objectEnumerator];
+ NSString* currentValue = NULL;
+ while ((currentValue = [exceptionEnumerator nextObject])) {
+ NS_ENSURE_TRUE([currentValue isKindOfClass:[NSString class]], false);
+ nsAutoCString overrideStr([currentValue UTF8String]);
+ if (mozilla::toolkit::system::IsHostProxyEntry(aHost, overrideStr))
+ return true;
+ }
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(false);
+}
+
+nsresult nsOSXSystemProxySettings::GetPACURI(nsACString& aResult) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ NS_ENSURE_TRUE(mProxyDict != NULL, NS_ERROR_FAILURE);
+
+ nsAutoCString pacUrl;
+ if (IsAutoconfigEnabled() && NS_SUCCEEDED(GetAutoconfigURL(pacUrl))) {
+ aResult.Assign(pacUrl);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+nsresult nsOSXSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
+ const nsACString& aScheme,
+ const nsACString& aHost,
+ const int32_t aPort,
+ nsACString& aResult) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ int32_t proxyPort;
+ nsAutoCString proxyHost;
+ bool proxySocks;
+ nsresult rv = FindSCProxyPort(aScheme, proxyHost, proxyPort, proxySocks);
+
+ if (NS_FAILED(rv) || IsInExceptionList(aHost)) {
+ aResult.AssignLiteral("DIRECT");
+ } else if (proxySocks) {
+ aResult.Assign("SOCKS "_ns + proxyHost + nsPrintfCString(":%d", proxyPort));
+ } else {
+ aResult.Assign("PROXY "_ns + proxyHost + nsPrintfCString(":%d", proxyPort));
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+using namespace mozilla::net;
+
+class OSXSystemProxySettingsAsync final : public nsOSXSystemProxySettings {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(OSXSystemProxySettingsAsync,
+ nsOSXSystemProxySettings)
+ NS_DECL_NSISYSTEMPROXYSETTINGS
+
+ OSXSystemProxySettingsAsync();
+
+ protected:
+ virtual void OnProxyConfigChangedInternal() override;
+ void InitDone() override;
+
+ private:
+ virtual ~OSXSystemProxySettingsAsync();
+
+ ProxyConfig mConfig;
+};
+
+OSXSystemProxySettingsAsync::OSXSystemProxySettingsAsync() = default;
+
+OSXSystemProxySettingsAsync::~OSXSystemProxySettingsAsync() = default;
+
+void OSXSystemProxySettingsAsync::InitDone() { OnProxyConfigChangedInternal(); }
+
+void OSXSystemProxySettingsAsync::OnProxyConfigChangedInternal() {
+ ProxyConfig config;
+
+ // PAC
+ nsAutoCString pacUrl;
+ if (IsAutoconfigEnabled() && NS_SUCCEEDED(GetAutoconfigURL(pacUrl))) {
+ config.SetPACUrl(pacUrl);
+ }
+
+ // proxies (for now: PROXY and SOCKS)
+ for (const SchemeMapping* keys = gSchemeMappingList; keys->mScheme != NULL;
+ ++keys) {
+ // Check the proxy is enabled
+ NSNumber* enabled = [mProxyDict objectForKey:(NSString*)keys->mEnabled];
+ if (!(enabled == NULL || [enabled isKindOfClass:[NSNumber class]])) {
+ continue;
+ }
+
+ if ([enabled intValue] == 0) {
+ continue;
+ }
+
+ // Get the proxy host
+ NSString* host = [mProxyDict objectForKey:(NSString*)keys->mHost];
+ if (host == NULL) break;
+ if (!([host isKindOfClass:[NSString class]])) {
+ continue;
+ }
+
+ nsCString resultHost;
+ resultHost.Assign([host UTF8String]);
+
+ // Get the proxy port
+ NSNumber* port = [mProxyDict objectForKey:(NSString*)keys->mPort];
+ if (!([port isKindOfClass:[NSNumber class]])) {
+ continue;
+ }
+
+ int32_t resultPort = [port intValue];
+ ProxyServer server(ProxyConfig::ToProxyType(keys->mScheme), resultHost,
+ resultPort);
+ config.Rules().mProxyServers[server.Type()] = std::move(server);
+ }
+
+ // exceptions
+ NSArray* exceptionList =
+ [mProxyDict objectForKey:(NSString*)kSCPropNetProxiesExceptionsList];
+ if (exceptionList != NULL && [exceptionList isKindOfClass:[NSArray class]]) {
+ NSEnumerator* exceptionEnumerator = [exceptionList objectEnumerator];
+ NSString* currentValue = NULL;
+ while ((currentValue = [exceptionEnumerator nextObject])) {
+ if (currentValue != NULL &&
+ [currentValue isKindOfClass:[NSString class]]) {
+ nsCString overrideStr([currentValue UTF8String]);
+ config.ByPassRules().mExceptions.AppendElement(std::move(overrideStr));
+ }
+ }
+ }
+
+ mConfig = std::move(config);
+}
+
+NS_IMETHODIMP
+OSXSystemProxySettingsAsync::GetMainThreadOnly(bool* aMainThreadOnly) {
+ return nsOSXSystemProxySettings::GetMainThreadOnly(aMainThreadOnly);
+}
+
+NS_IMETHODIMP
+OSXSystemProxySettingsAsync::GetPACURI(nsACString& aResult) {
+ aResult.Assign(mConfig.PACUrl());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OSXSystemProxySettingsAsync::GetProxyForURI(const nsACString& aSpec,
+ const nsACString& aScheme,
+ const nsACString& aHost,
+ const int32_t aPort,
+ nsACString& aResult) {
+ for (const auto& bypassRule : mConfig.ByPassRules().mExceptions) {
+ if (mozilla::toolkit::system::IsHostProxyEntry(aHost, bypassRule)) {
+ aResult.AssignLiteral("DIRECT");
+ return NS_OK;
+ }
+ }
+
+ mConfig.GetProxyString(aScheme, aResult);
+ return NS_OK;
+}
+
+NS_IMPL_COMPONENT_FACTORY(nsOSXSystemProxySettings) {
+ auto settings =
+ mozilla::StaticPrefs::network_proxy_detect_system_proxy_changes()
+ ? mozilla::MakeRefPtr<OSXSystemProxySettingsAsync>()
+ : mozilla::MakeRefPtr<nsOSXSystemProxySettings>();
+ if (NS_SUCCEEDED(settings->Init())) {
+ return settings.forget().downcast<nsISupports>();
+ }
+ return nullptr;
+}
diff --git a/toolkit/system/unixproxy/components.conf b/toolkit/system/unixproxy/components.conf
new file mode 100644
index 0000000000..4f8f11c82a
--- /dev/null
+++ b/toolkit/system/unixproxy/components.conf
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{0fa3158c-d5a7-43de-9181-a285e74cf1d4}',
+ 'contract_ids': ['@mozilla.org/system-proxy-settings;1'],
+ 'type': 'nsUnixSystemProxySettings',
+ },
+]
diff --git a/toolkit/system/unixproxy/moz.build b/toolkit/system/unixproxy/moz.build
new file mode 100644
index 0000000000..e5d9eb046e
--- /dev/null
+++ b/toolkit/system/unixproxy/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking")
+
+if CONFIG["MOZ_ENABLE_LIBPROXY"]:
+ CXXFLAGS += CONFIG["MOZ_LIBPROXY_CFLAGS"]
+ SOURCES += [
+ "nsLibProxySettings.cpp",
+ ]
+else:
+ SOURCES += [
+ "nsUnixSystemProxySettings.cpp",
+ ]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/toolkit/system/unixproxy/nsLibProxySettings.cpp b/toolkit/system/unixproxy/nsLibProxySettings.cpp
new file mode 100644
index 0000000000..4f6f43fb76
--- /dev/null
+++ b/toolkit/system/unixproxy/nsLibProxySettings.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISystemProxySettings.h"
+#include "mozilla/Components.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nspr.h"
+
+extern "C" {
+#include <proxy.h>
+}
+
+class nsUnixSystemProxySettings : public nsISystemProxySettings {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISYSTEMPROXYSETTINGS
+
+ nsUnixSystemProxySettings() { mProxyFactory = nullptr; }
+
+ private:
+ ~nsUnixSystemProxySettings() {
+ if (mProxyFactory) px_proxy_factory_free(mProxyFactory);
+ }
+
+ pxProxyFactory* mProxyFactory;
+};
+
+NS_IMPL_ISUPPORTS(nsUnixSystemProxySettings, nsISystemProxySettings)
+
+NS_IMETHODIMP
+nsUnixSystemProxySettings::GetMainThreadOnly(bool* aMainThreadOnly) {
+ *aMainThreadOnly = false;
+ return NS_OK;
+}
+
+nsresult nsUnixSystemProxySettings::GetPACURI(nsACString& aResult) {
+ // Make sure we return an empty result.
+ aResult.Truncate();
+ return NS_OK;
+}
+
+nsresult nsUnixSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
+ const nsACString& aScheme,
+ const nsACString& aHost,
+ const int32_t aPort,
+ nsACString& aResult) {
+ nsresult rv;
+
+ if (!mProxyFactory) {
+ mProxyFactory = px_proxy_factory_new();
+ }
+ NS_ENSURE_TRUE(mProxyFactory, NS_ERROR_NOT_AVAILABLE);
+
+ char** proxyArray = nullptr;
+ proxyArray = px_proxy_factory_get_proxies(mProxyFactory,
+ PromiseFlatCString(aSpec).get());
+ NS_ENSURE_TRUE(proxyArray, NS_ERROR_NOT_AVAILABLE);
+
+ // Translate libproxy's output to PAC string as expected
+ // libproxy returns an array of proxies in the format:
+ // <procotol>://[username:password@]proxy:port
+ // or
+ // direct://
+ //
+ // PAC format: "PROXY proxy1.foo.com:8080; PROXY proxy2.foo.com:8080; DIRECT"
+ // but nsISystemProxySettings allows "PROXY http://proxy.foo.com:8080" as
+ // well.
+
+ int c = 0;
+ while (proxyArray[c] != nullptr) {
+ if (!aResult.IsEmpty()) {
+ aResult.AppendLiteral("; ");
+ }
+
+ // figure out the scheme, and we can't use nsIIOService::NewURI because
+ // this is not the main thread.
+ char* colon = strchr(proxyArray[c], ':');
+ uint32_t schemelen = colon ? colon - proxyArray[c] : 0;
+ if (schemelen < 1) {
+ c++;
+ continue;
+ }
+
+ if (schemelen == 6 && !strncasecmp(proxyArray[c], "direct", 6)) {
+ aResult.AppendLiteral("DIRECT");
+ } else {
+ aResult.AppendLiteral("PROXY ");
+ aResult.Append(proxyArray[c]);
+ }
+
+ c++;
+ }
+
+ free(proxyArray);
+ return NS_OK;
+}
+
+NS_IMPL_COMPONENT_FACTORY(nsUnixSystemProxySettings) {
+ return do_AddRef(new nsUnixSystemProxySettings()).downcast<nsISupports>();
+}
diff --git a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
new file mode 100644
index 0000000000..1b6d6223c4
--- /dev/null
+++ b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISystemProxySettings.h"
+#include "mozilla/Components.h"
+#include "nsIURI.h"
+#include "nsArrayUtils.h"
+#include "prnetdb.h"
+#include "prenv.h"
+#include "nsInterfaceHashtable.h"
+#include "nsHashtablesFwd.h"
+#include "nsHashKeys.h"
+#include "nsNetUtil.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIGSettingsService.h"
+#include "nsReadableUtils.h"
+
+using namespace mozilla;
+
+class nsUnixSystemProxySettings final : public nsISystemProxySettings {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISYSTEMPROXYSETTINGS
+
+ nsUnixSystemProxySettings() : mSchemeProxySettings(4) {}
+ void Init();
+
+ private:
+ ~nsUnixSystemProxySettings() = default;
+
+ nsCOMPtr<nsIGSettingsService> mGSettings;
+ nsCOMPtr<nsIGSettingsCollection> mProxySettings;
+ nsInterfaceHashtable<nsCStringHashKey, nsIGSettingsCollection>
+ mSchemeProxySettings;
+ nsresult GetProxyFromGSettings(const nsACString& aScheme,
+ const nsACString& aHost, int32_t aPort,
+ nsACString& aResult);
+ nsresult SetProxyResultFromGSettings(const char* aKeyBase, const char* aType,
+ nsACString& aResult);
+};
+
+NS_IMPL_ISUPPORTS(nsUnixSystemProxySettings, nsISystemProxySettings)
+
+NS_IMETHODIMP
+nsUnixSystemProxySettings::GetMainThreadOnly(bool* aMainThreadOnly) {
+ // dbus prevents us from being threadsafe, but this routine should not block
+ // anyhow
+ *aMainThreadOnly = true;
+ return NS_OK;
+}
+
+void nsUnixSystemProxySettings::Init() {
+ mGSettings = do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+ if (mGSettings) {
+ mGSettings->GetCollectionForSchema("org.gnome.system.proxy"_ns,
+ getter_AddRefs(mProxySettings));
+ }
+}
+
+nsresult nsUnixSystemProxySettings::GetPACURI(nsACString& aResult) {
+ if (mProxySettings) {
+ nsCString proxyMode;
+ // Check if mode is auto
+ nsresult rv = mProxySettings->GetString("mode"_ns, proxyMode);
+ if (rv == NS_OK && proxyMode.EqualsLiteral("auto")) {
+ return mProxySettings->GetString("autoconfig-url"_ns, aResult);
+ }
+ }
+
+ // Return an empty string when auto mode is not set.
+ aResult.Truncate();
+ return NS_OK;
+}
+
+static bool IsInNoProxyList(const nsACString& aHost, int32_t aPort,
+ const char* noProxyVal) {
+ NS_ASSERTION(aPort >= 0, "Negative port?");
+
+ nsAutoCString noProxy(noProxyVal);
+ if (noProxy.EqualsLiteral("*")) return true;
+
+ noProxy.StripWhitespace();
+
+ nsReadingIterator<char> pos;
+ nsReadingIterator<char> end;
+ noProxy.BeginReading(pos);
+ noProxy.EndReading(end);
+ while (pos != end) {
+ nsReadingIterator<char> last = pos;
+ nsReadingIterator<char> nextPos;
+ if (FindCharInReadable(',', last, end)) {
+ nextPos = last;
+ ++nextPos;
+ } else {
+ last = end;
+ nextPos = end;
+ }
+
+ nsReadingIterator<char> colon = pos;
+ int32_t port = -1;
+ if (FindCharInReadable(':', colon, last)) {
+ ++colon;
+ nsDependentCSubstring portStr(colon, last);
+ nsAutoCString portStr2(
+ portStr); // We need this for ToInteger. String API's suck.
+ nsresult err;
+ port = portStr2.ToInteger(&err);
+ if (NS_FAILED(err)) {
+ port = -2; // don't match any port, so we ignore this pattern
+ }
+ --colon;
+ } else {
+ colon = last;
+ }
+
+ if (port == -1 || port == aPort) {
+ nsDependentCSubstring hostStr(pos, colon);
+ // By using StringEndsWith instead of an equality comparator, we can
+ // include sub-domains
+ if (StringEndsWith(aHost, hostStr, nsCaseInsensitiveCStringComparator))
+ return true;
+ }
+
+ pos = nextPos;
+ }
+
+ return false;
+}
+
+static void SetProxyResult(const char* aType, const nsACString& aHost,
+ int32_t aPort, nsACString& aResult) {
+ aResult.AssignASCII(aType);
+ aResult.Append(' ');
+ aResult.Append(aHost);
+ if (aPort > 0) {
+ aResult.Append(':');
+ aResult.AppendInt(aPort);
+ }
+}
+
+static void SetProxyResultDirect(nsACString& aResult) {
+ aResult.AssignLiteral("DIRECT");
+}
+
+static nsresult GetProxyFromEnvironment(const nsACString& aScheme,
+ const nsACString& aHost, int32_t aPort,
+ nsACString& aResult) {
+ nsAutoCString envVar;
+ envVar.Append(aScheme);
+ envVar.AppendLiteral("_proxy");
+ const char* proxyVal = PR_GetEnv(envVar.get());
+ if (!proxyVal) {
+ // try uppercase name too
+ ToUpperCase(envVar);
+ proxyVal = PR_GetEnv(envVar.get());
+ }
+ if (!proxyVal) {
+ proxyVal = PR_GetEnv("all_proxy");
+ if (!proxyVal) {
+ // try uppercase name too
+ proxyVal = PR_GetEnv("ALL_PROXY");
+ }
+ if (!proxyVal) {
+ // Return failure so that the caller can detect the failure and
+ // fall back to other proxy detection (e.g., WPAD)
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ const char* noProxyVal = PR_GetEnv("no_proxy");
+ if (!noProxyVal) {
+ // try uppercase name too
+ noProxyVal = PR_GetEnv("NO_PROXY");
+ }
+ if (noProxyVal && IsInNoProxyList(aHost, aPort, noProxyVal)) {
+ SetProxyResultDirect(aResult);
+ return NS_OK;
+ }
+
+ // Use our URI parser to crack the proxy URI
+ nsCOMPtr<nsIURI> proxyURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(proxyURI), proxyVal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Is there a way to specify "socks://" or something in these environment
+ // variables? I can't find any documentation.
+ if (!proxyURI->SchemeIs("http")) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+
+ nsAutoCString proxyHost;
+ rv = proxyURI->GetHost(proxyHost);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t proxyPort;
+ rv = proxyURI->GetPort(&proxyPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetProxyResult("PROXY", proxyHost, proxyPort, aResult);
+ return NS_OK;
+}
+
+nsresult nsUnixSystemProxySettings::SetProxyResultFromGSettings(
+ const char* aKeyBase, const char* aType, nsACString& aResult) {
+ nsDependentCString key(aKeyBase);
+
+ nsCOMPtr<nsIGSettingsCollection> proxy_settings =
+ mSchemeProxySettings.Get(key);
+ nsresult rv;
+ if (!proxy_settings) {
+ rv =
+ mGSettings->GetCollectionForSchema(key, getter_AddRefs(proxy_settings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSchemeProxySettings.InsertOrUpdate(key, proxy_settings);
+ }
+
+ nsAutoCString host;
+ rv = proxy_settings->GetString("host"_ns, host);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (host.IsEmpty()) return NS_ERROR_FAILURE;
+
+ int32_t port;
+ rv = proxy_settings->GetInt("port"_ns, &port);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* When port is 0, proxy is not considered as enabled even if host is set. */
+ if (port == 0) return NS_ERROR_FAILURE;
+
+ SetProxyResult(aType, host, port, aResult);
+ return NS_OK;
+}
+
+/* copied from nsProtocolProxyService.cpp --- we should share this! */
+static void proxy_MaskIPv6Addr(PRIPv6Addr& addr, uint16_t mask_len) {
+ if (mask_len == 128) return;
+
+ if (mask_len > 96) {
+ addr.pr_s6_addr32[3] =
+ PR_htonl(PR_ntohl(addr.pr_s6_addr32[3]) & (~0L << (128 - mask_len)));
+ } else if (mask_len > 64) {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] =
+ PR_htonl(PR_ntohl(addr.pr_s6_addr32[2]) & (~0L << (96 - mask_len)));
+ } else if (mask_len > 32) {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = 0;
+ addr.pr_s6_addr32[1] =
+ PR_htonl(PR_ntohl(addr.pr_s6_addr32[1]) & (~0L << (64 - mask_len)));
+ } else {
+ addr.pr_s6_addr32[3] = 0;
+ addr.pr_s6_addr32[2] = 0;
+ addr.pr_s6_addr32[1] = 0;
+ addr.pr_s6_addr32[0] =
+ PR_htonl(PR_ntohl(addr.pr_s6_addr32[0]) & (~0L << (32 - mask_len)));
+ }
+}
+
+static bool ConvertToIPV6Addr(const nsACString& aName, PRIPv6Addr* aAddr,
+ int32_t* aMask) {
+ PRNetAddr addr;
+ // try to convert hostname to IP
+ if (PR_StringToNetAddr(PromiseFlatCString(aName).get(), &addr) != PR_SUCCESS)
+ return false;
+
+ // convert parsed address to IPv6
+ if (addr.raw.family == PR_AF_INET) {
+ // convert to IPv4-mapped address
+ PR_ConvertIPv4AddrToIPv6(addr.inet.ip, aAddr);
+ if (aMask) {
+ if (*aMask <= 32)
+ *aMask += 96;
+ else
+ return false;
+ }
+ } else if (addr.raw.family == PR_AF_INET6) {
+ // copy the address
+ memcpy(aAddr, &addr.ipv6.ip, sizeof(PRIPv6Addr));
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+static bool HostIgnoredByProxy(const nsACString& aIgnore,
+ const nsACString& aHost) {
+ if (aIgnore.Equals(aHost, nsCaseInsensitiveCStringComparator)) return true;
+
+ if (aIgnore.First() == '*' &&
+ StringEndsWith(aHost, nsDependentCSubstring(aIgnore, 1),
+ nsCaseInsensitiveCStringComparator))
+ return true;
+
+ int32_t mask = 128;
+ nsReadingIterator<char> start;
+ nsReadingIterator<char> slash;
+ nsReadingIterator<char> end;
+ aIgnore.BeginReading(start);
+ aIgnore.BeginReading(slash);
+ aIgnore.EndReading(end);
+ if (FindCharInReadable('/', slash, end)) {
+ ++slash;
+ nsDependentCSubstring maskStr(slash, end);
+ nsAutoCString maskStr2(maskStr);
+ nsresult err;
+ mask = maskStr2.ToInteger(&err);
+ if (NS_FAILED(err)) {
+ mask = 128;
+ }
+ --slash;
+ } else {
+ slash = end;
+ }
+
+ nsDependentCSubstring ignoreStripped(start, slash);
+ PRIPv6Addr ignoreAddr, hostAddr;
+ if (!ConvertToIPV6Addr(ignoreStripped, &ignoreAddr, &mask) ||
+ !ConvertToIPV6Addr(aHost, &hostAddr, nullptr))
+ return false;
+
+ proxy_MaskIPv6Addr(ignoreAddr, mask);
+ proxy_MaskIPv6Addr(hostAddr, mask);
+
+ return memcmp(&ignoreAddr, &hostAddr, sizeof(PRIPv6Addr)) == 0;
+}
+
+nsresult nsUnixSystemProxySettings::GetProxyFromGSettings(
+ const nsACString& aScheme, const nsACString& aHost, int32_t aPort,
+ nsACString& aResult) {
+ nsCString proxyMode;
+ nsresult rv = mProxySettings->GetString("mode"_ns, proxyMode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // return NS_ERROR_FAILURE when no proxy is set
+ if (!proxyMode.EqualsLiteral("manual")) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIArray> ignoreList;
+ if (NS_SUCCEEDED(mProxySettings->GetStringList("ignore-hosts"_ns,
+ getter_AddRefs(ignoreList))) &&
+ ignoreList) {
+ uint32_t len = 0;
+ ignoreList->GetLength(&len);
+ for (uint32_t i = 0; i < len; ++i) {
+ nsCOMPtr<nsISupportsCString> str = do_QueryElementAt(ignoreList, i);
+ if (str) {
+ nsCString s;
+ if (NS_SUCCEEDED(str->GetData(s)) && !s.IsEmpty()) {
+ if (HostIgnoredByProxy(s, aHost)) {
+ SetProxyResultDirect(aResult);
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+
+ if (aScheme.LowerCaseEqualsLiteral("http")) {
+ rv = SetProxyResultFromGSettings("org.gnome.system.proxy.http", "PROXY",
+ aResult);
+ } else if (aScheme.LowerCaseEqualsLiteral("https")) {
+ rv = SetProxyResultFromGSettings("org.gnome.system.proxy.https", "PROXY",
+ aResult);
+ /* Try to use HTTP proxy when HTTPS proxy is not explicitly defined */
+ if (rv != NS_OK)
+ rv = SetProxyResultFromGSettings("org.gnome.system.proxy.http", "PROXY",
+ aResult);
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+ if (rv != NS_OK) {
+ /* If proxy for scheme is not specified, use SOCKS proxy for all schemes */
+ rv = SetProxyResultFromGSettings("org.gnome.system.proxy.socks", "SOCKS",
+ aResult);
+ }
+
+ if (NS_FAILED(rv)) {
+ SetProxyResultDirect(aResult);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsUnixSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
+ const nsACString& aScheme,
+ const nsACString& aHost,
+ const int32_t aPort,
+ nsACString& aResult) {
+ if (mProxySettings) {
+ nsresult rv = GetProxyFromGSettings(aScheme, aHost, aPort, aResult);
+ if (NS_SUCCEEDED(rv)) return rv;
+ }
+
+ return GetProxyFromEnvironment(aScheme, aHost, aPort, aResult);
+}
+
+NS_IMPL_COMPONENT_FACTORY(nsUnixSystemProxySettings) {
+ auto result = MakeRefPtr<nsUnixSystemProxySettings>();
+ result->Init();
+ return result.forget().downcast<nsISupports>();
+}
diff --git a/toolkit/system/windowsDHCPClient/DHCPUtils.cpp b/toolkit/system/windowsDHCPClient/DHCPUtils.cpp
new file mode 100644
index 0000000000..956c2adbf0
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/DHCPUtils.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DHCPUtils.h"
+#include <vector>
+#include "mozilla/Logging.h"
+#include "nsString.h"
+
+#define MOZ_WORKING_BUFFER_SIZE_NETWORK_ADAPTERS 15000
+#define MOZ_WORKING_BUFFER_SIZE_DHCP_PARAMS 1000
+#define MOZ_MAX_TRIES 3
+namespace mozilla {
+namespace toolkit {
+namespace system {
+namespace windowsDHCPClient {
+
+//
+// The comments on this page reference the following Microsoft documentation
+// pages (both retrieved 2017-06-27)
+// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365915(v=vs.85).aspx
+// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363298(v=vs.85).aspx
+mozilla::LazyLogModule gDhcpUtilsLog("dhcpUtils");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gDhcpUtilsLog, LogLevel::Debug, args)
+
+bool IsCurrentAndHasDHCP(PIP_ADAPTER_ADDRESSES aAddresses) {
+ return aAddresses->OperStatus == 1 &&
+ (aAddresses->Dhcpv4Server.iSockaddrLength ||
+ aAddresses->Dhcpv6Server.iSockaddrLength);
+}
+
+nsresult GetActiveDHCPNetworkAdapterName(
+ nsACString& aNetworkAdapterName,
+ WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper) {
+ /* Declare and initialize variables */
+
+ uint32_t dwRetVal = 0;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ // Set the flags to pass to GetAdaptersAddresses
+ uint32_t flags = GAA_FLAG_INCLUDE_PREFIX;
+
+ // default to unspecified address family (both)
+ uint32_t family = AF_UNSPEC;
+
+ // Allocate a 15 KB buffer to start with.
+ uint32_t outBufLen = MOZ_WORKING_BUFFER_SIZE_NETWORK_ADAPTERS;
+ uint32_t iterations = 0;
+
+ aNetworkAdapterName.Truncate();
+
+ // Now we try calling the GetAdaptersAddresses method until the return value
+ // is not ERROR_BUFFER_OVERFLOW. According to [1]
+ //
+ //
+ // > When the return value is ERROR_BUFFER_OVERFLOW, the SizePointer parameter
+ // returned > points to the required size of the buffer to hold the adapter
+ // information. > Note that it is possible for the buffer size required for
+ // the IP_ADAPTER_ADDRESSES > structures pointed to by the AdapterAddresses
+ // parameter to change between > subsequent calls to the GetAdaptersAddresses
+ // function if an adapter address > is added or removed. However, this method
+ // of using the GetAdaptersAddresses > function is strongly discouraged. This
+ // method requires calling the > GetAdaptersAddresses function multiple times.
+ // >
+ // > The recommended method of calling the GetAdaptersAddresses function is
+ // > to pre-allocate a 15KB working buffer pointed to by the AdapterAddresses
+ // parameter. > On typical computers, this dramatically reduces the chances
+ // that the > GetAdaptersAddresses function returns ERROR_BUFFER_OVERFLOW,
+ // which would require > calling GetAdaptersAddresses function multiple times.
+ //
+ //
+ // The possibility of the buffer size changing between calls to
+ // GetAdaptersAddresses is why we allow the following code to be called
+ // several times, rather than just the two that would be neccessary if we
+ // could rely on the value returned in outBufLen being the true size needed.
+
+ std::vector<IP_ADAPTER_ADDRESSES> pAddresses;
+ do {
+ // resize outBufLen up to the next multiple of sizeof(IP_ADAPTER_ADDRESSES)
+ outBufLen = ((outBufLen + sizeof(IP_ADAPTER_ADDRESSES) - 1) /
+ sizeof(IP_ADAPTER_ADDRESSES)) *
+ sizeof(IP_ADAPTER_ADDRESSES);
+ pAddresses.resize(outBufLen / sizeof(IP_ADAPTER_ADDRESSES));
+ LOG(
+ ("Trying GetAdaptersAddresses with pAddresses sized to %zu and buffer "
+ "length of %d",
+ pAddresses.size(), outBufLen));
+
+ dwRetVal = aWindowsNetworkFunctionsWrapper->GetAdaptersAddressesWrapped(
+ family, flags, nullptr, pAddresses.data(), (PULONG)&outBufLen);
+
+ if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
+ iterations++;
+ }
+ } while (dwRetVal == ERROR_BUFFER_OVERFLOW && iterations < MOZ_MAX_TRIES);
+
+ switch (dwRetVal) {
+ case NO_ERROR: {
+ // set default return value if we don't find a suitable network adapter
+ rv = NS_ERROR_NOT_AVAILABLE;
+ PIP_ADAPTER_ADDRESSES pCurrAddresses = pAddresses.data();
+ while (pCurrAddresses) {
+ if (IsCurrentAndHasDHCP(pCurrAddresses)) {
+ rv = NS_OK;
+ aNetworkAdapterName.Assign(pCurrAddresses->AdapterName);
+ break;
+ }
+ pCurrAddresses = pCurrAddresses->Next;
+ }
+ } break;
+ case ERROR_NO_DATA:
+ rv = NS_ERROR_NOT_AVAILABLE;
+ break;
+ default:
+ MOZ_LOG(gDhcpUtilsLog, mozilla::LogLevel::Warning,
+ ("GetAdaptersAddresses returned %d", dwRetVal));
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ return rv;
+}
+
+DWORD
+IterateDHCPInformRequestsUntilBufferLargeEnough(
+ DHCPCAPI_PARAMS& aDhcpRequestedOptionParams,
+ wchar_t* aWideNetworkAdapterName, std::vector<char>& aBuffer,
+ WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper) {
+ uint32_t iterations = 0;
+ uint32_t outBufLen = MOZ_WORKING_BUFFER_SIZE_DHCP_PARAMS;
+
+ DHCPCAPI_PARAMS_ARRAY RequestParams = {1, // only one option to request
+ &aDhcpRequestedOptionParams};
+
+ // According to [2],
+ // the following is for 'Optional data to be requested,
+ // in addition to the data requested in the RecdParams array.'
+ // We are not requesting anything in addition, so this is empty.
+ DHCPCAPI_PARAMS_ARRAY SendParams = {0, nullptr};
+
+ DWORD winAPIResponse;
+ // Now we try calling the DHCPRequestParams method until the return value
+ // is not ERROR_MORE_DATA. According to [2]:
+ //
+ //
+ // > Note that the required size of Buffer may increase during the time that
+ // elapses > between the initial function call's return and a subsequent call;
+ // > therefore, the required size of Buffer (indicated in pSize)
+ // > provides an indication of the approximate size required of Buffer,
+ // > rather than guaranteeing that subsequent calls will return successfully
+ // > if Buffer is set to the size indicated in pSize.
+ //
+ //
+ // This is why we allow this DHCPRequestParams to be called several times,
+ // rather than just the two that would be neccessary if we could rely on the
+ // value returned in outBufLen being the true size needed.
+ do {
+ aBuffer.resize(outBufLen);
+
+ winAPIResponse = aWindowsNetworkFunctionsWrapper->DhcpRequestParamsWrapped(
+ DHCPCAPI_REQUEST_SYNCHRONOUS, // Flags
+ nullptr, // Reserved
+ aWideNetworkAdapterName, // Adapter Name
+ nullptr, // not using class id
+ SendParams, // sent parameters
+ RequestParams, // requesting params
+ (PBYTE)aBuffer.data(), // buffer for the output of RequestParams
+ (PULONG)&outBufLen, // buffer size
+ nullptr // Request ID for persistent requests - not needed here
+ );
+
+ if (winAPIResponse == ERROR_MORE_DATA) {
+ iterations++;
+ }
+ } while (winAPIResponse == ERROR_MORE_DATA && iterations < MOZ_MAX_TRIES);
+ return winAPIResponse;
+}
+
+nsresult RetrieveOption(
+ const nsACString& aAdapterName, uint8_t aOption,
+ std::vector<char>& aOptionValueBuf, uint32_t* aOptionSize,
+ WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper) {
+ nsresult rv;
+ nsAutoString wideNetworkAdapterName = NS_ConvertUTF8toUTF16(aAdapterName);
+
+ DHCPCAPI_PARAMS DhcpRequestedOptionParams = {
+ 0, // Flags - Reserved, must be set to zero [2]
+ aOption, // OptionId
+ false, // whether this is vendor specific - let's assume not
+ nullptr, // data filled in on return
+ 0 // nBytes used by return data
+ };
+
+ std::vector<char> tmpBuffer(
+ MOZ_WORKING_BUFFER_SIZE_DHCP_PARAMS); // a buffer for the DHCP response
+ // object
+ DWORD winAPIResponse = IterateDHCPInformRequestsUntilBufferLargeEnough(
+ DhcpRequestedOptionParams, wideNetworkAdapterName.get(), tmpBuffer,
+ aWindowsNetworkFunctionsWrapper);
+
+ switch (winAPIResponse) {
+ case NO_ERROR: {
+ if (DhcpRequestedOptionParams.nBytesData == 0) {
+ *aOptionSize = 0;
+ rv = NS_ERROR_NOT_AVAILABLE;
+ break;
+ }
+
+ if (*aOptionSize >= DhcpRequestedOptionParams.nBytesData) {
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
+ }
+
+ uint32_t actualSizeReturned =
+ *aOptionSize > DhcpRequestedOptionParams.nBytesData
+ ? DhcpRequestedOptionParams.nBytesData
+ : *aOptionSize;
+
+ memcpy(aOptionValueBuf.data(), DhcpRequestedOptionParams.Data,
+ actualSizeReturned);
+ *aOptionSize = DhcpRequestedOptionParams.nBytesData;
+ break;
+ }
+ case ERROR_INVALID_PARAMETER:
+ MOZ_LOG(gDhcpUtilsLog, mozilla::LogLevel::Warning,
+ ("RetrieveOption returned %lu (ERROR_INVALID_PARAMETER) when "
+ "option %d requested",
+ winAPIResponse, aOption));
+ rv = NS_ERROR_INVALID_ARG;
+ break;
+ default:
+ MOZ_LOG(gDhcpUtilsLog, mozilla::LogLevel::Warning,
+ ("RetrieveOption returned %lu when option %d requested",
+ winAPIResponse, aOption));
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+} // namespace windowsDHCPClient
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
diff --git a/toolkit/system/windowsDHCPClient/DHCPUtils.h b/toolkit/system/windowsDHCPClient/DHCPUtils.h
new file mode 100644
index 0000000000..20f2996acb
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/DHCPUtils.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_toolkit_system_windowsDHCPClient_DHCPUtils_h
+#define mozilla_toolkit_system_windowsDHCPClient_DHCPUtils_h
+
+#include "WindowsNetworkFunctionsWrapper.h"
+#include <vector>
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+namespace windowsDHCPClient {
+
+nsresult GetActiveDHCPNetworkAdapterName(
+ nsACString& aNetworkAdapterName,
+ WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper);
+
+nsresult RetrieveOption(
+ const nsACString& aAdapterName, uint8_t aOption,
+ std::vector<char>& aOptionValueBuf, uint32_t* aOptionSize,
+ WindowsNetworkFunctionsWrapper* aWindowsNetworkFunctionsWrapper);
+
+} // namespace windowsDHCPClient
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
+#endif // mozilla_toolkit_system_windowsDHCPClient_DHCPUtils_h
diff --git a/toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.cpp b/toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.cpp
new file mode 100644
index 0000000000..0dac4bcbb9
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.cpp
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WindowsNetworkFunctionsWrapper.h"
+
+#ifndef __MINGW32__
+# pragma comment(lib, "IPHLPAPI.lib")
+# pragma comment(lib, "dhcpcsvc.lib")
+#endif
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+namespace windowsDHCPClient {
+
+NS_IMPL_ISUPPORTS(WindowsNetworkFunctionsWrapper, nsISupports)
+
+ULONG WindowsNetworkFunctionsWrapper::GetAdaptersAddressesWrapped(
+ _In_ ULONG aFamily, _In_ ULONG aFlags, _In_ PVOID aReserved,
+ _Inout_ PIP_ADAPTER_ADDRESSES aAdapterAddresses,
+ _Inout_ PULONG aSizePointer) {
+ return GetAdaptersAddresses(aFamily, aFlags, aReserved, aAdapterAddresses,
+ aSizePointer);
+}
+
+DWORD WindowsNetworkFunctionsWrapper::DhcpRequestParamsWrapped(
+ _In_ DWORD aFlags, _In_ LPVOID aReserved, _In_ LPWSTR aAdapterName,
+ _In_ LPDHCPCAPI_CLASSID aClassId, _In_ DHCPCAPI_PARAMS_ARRAY aSendParams,
+ _Inout_ DHCPCAPI_PARAMS_ARRAY aRecdParams, _In_ LPBYTE aBuffer,
+ _Inout_ LPDWORD apSize, _In_ LPWSTR aRequestIdStr) {
+ return DhcpRequestParams(aFlags, aReserved, aAdapterName, aClassId,
+ aSendParams, aRecdParams, aBuffer, apSize,
+ aRequestIdStr);
+}
+} // namespace windowsDHCPClient
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
diff --git a/toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.h b/toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.h
new file mode 100644
index 0000000000..2da6a9ff33
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/WindowsNetworkFunctionsWrapper.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_toolkit_system_windowsDHCPClient_windowsNetworkFunctionsWrapper_h
+#define mozilla_toolkit_system_windowsDHCPClient_windowsNetworkFunctionsWrapper_h
+
+#include <winsock2.h> // there is a compilation error if Winsock.h is not
+ // declared before dhcpcsdk.h
+#include <dhcpcsdk.h>
+#include <iphlpapi.h>
+
+#include "nsISupports.h"
+
+// Thin wrapper around low-level network functions needed for DHCP querying for
+// web proxy
+namespace mozilla {
+namespace toolkit {
+namespace system {
+namespace windowsDHCPClient {
+
+class WindowsNetworkFunctionsWrapper : nsISupports {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ WindowsNetworkFunctionsWrapper(){};
+
+ virtual ULONG GetAdaptersAddressesWrapped(
+ _In_ ULONG aFamily, _In_ ULONG aFlags, _In_ PVOID aReserved,
+ _Inout_ PIP_ADAPTER_ADDRESSES aAdapterAddresses,
+ _Inout_ PULONG aSizePointer);
+
+ virtual DWORD DhcpRequestParamsWrapped(
+ _In_ DWORD aFlags, _In_ LPVOID aReserved, _In_ LPWSTR aAdapterName,
+ _In_ LPDHCPCAPI_CLASSID aClassId, _In_ DHCPCAPI_PARAMS_ARRAY aSendParams,
+ _Inout_ DHCPCAPI_PARAMS_ARRAY aRecdParams, _In_ LPBYTE aBuffer,
+ _Inout_ LPDWORD apSize, _In_ LPWSTR aRequestIdStr);
+
+ protected:
+ virtual ~WindowsNetworkFunctionsWrapper(){};
+};
+
+} // namespace windowsDHCPClient
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
+#endif // mozilla_toolkit_system_windowsDHCPClient_windowsNetworkFunctionsWrapper_h
diff --git a/toolkit/system/windowsDHCPClient/components.conf b/toolkit/system/windowsDHCPClient/components.conf
new file mode 100644
index 0000000000..f2085bce3e
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/components.conf
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{febf1d69-4d7d-4891-9524-045ad18b5592}',
+ 'contract_ids': ['@mozilla.org/dhcp-client;1'],
+ 'type': 'mozilla::toolkit::system::windowsDHCPClient::nsWindowsDHCPClient',
+ 'headers': ['/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.h']
+ },
+]
diff --git a/toolkit/system/windowsDHCPClient/moz.build b/toolkit/system/windowsDHCPClient/moz.build
new file mode 100644
index 0000000000..308ac4dd4d
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: HTTP")
+
+TEST_DIRS += ["tests/gtest"]
+
+SOURCES += [
+ "DHCPUtils.cpp",
+ "nsWindowsDHCPClient.cpp",
+ "WindowsNetworkFunctionsWrapper.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.cpp b/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.cpp
new file mode 100644
index 0000000000..dae96cc309
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWindowsDHCPClient.h"
+
+#include <vector>
+
+#include "DHCPUtils.h"
+#include "nsNetCID.h"
+#include "nsString.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Components.h"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+namespace windowsDHCPClient {
+
+LazyLogModule gDhcpLog("windowsDHCPClient");
+
+#undef LOG
+#define LOG(args) MOZ_LOG(gDhcpLog, LogLevel::Debug, args)
+
+#define MOZ_MAX_DHCP_OPTION_LENGTH \
+ 255 // this is the maximum option length in DHCP V4 and 6
+
+NS_IMPL_ISUPPORTS(nsWindowsDHCPClient, nsIDHCPClient)
+
+NS_IMETHODIMP
+nsWindowsDHCPClient::GetOption(uint8_t aOption, nsACString& aRetVal) {
+ nsCString networkAdapterName;
+ nsresult rv;
+ rv = GetActiveDHCPNetworkAdapterName(networkAdapterName, mNetworkFunctions);
+ if (rv != NS_OK) {
+ LOG(
+ ("Failed to get network adapter name in nsWindowsDHCPClient::GetOption "
+ "due to error %d",
+ uint32_t(rv)));
+ return rv;
+ }
+
+ uint32_t sizeOptionValue = MOZ_MAX_DHCP_OPTION_LENGTH;
+ std::vector<char> optionValue;
+
+ bool retryingAfterLossOfSignificantData = false;
+ do {
+ optionValue.resize(sizeOptionValue);
+ rv = RetrieveOption(networkAdapterName, aOption, optionValue,
+ &sizeOptionValue, mNetworkFunctions);
+ if (rv == NS_ERROR_LOSS_OF_SIGNIFICANT_DATA) {
+ LOG((
+ "In nsWindowsDHCPClient::GetOption, DHCP Option %d required %d bytes",
+ aOption, sizeOptionValue));
+ if (retryingAfterLossOfSignificantData) {
+ break;
+ }
+ retryingAfterLossOfSignificantData = true;
+ }
+ } while (rv == NS_ERROR_LOSS_OF_SIGNIFICANT_DATA);
+ if (rv != NS_OK) {
+ LOG(
+ ("Failed to get DHCP Option %d nsWindowsDHCPClient::GetOption due to "
+ "error %d",
+ aOption, uint32_t(rv)));
+ return rv;
+ }
+ aRetVal.Assign(optionValue.data(), sizeOptionValue);
+ return NS_OK;
+}
+
+} // namespace windowsDHCPClient
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
diff --git a/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.h b/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.h
new file mode 100644
index 0000000000..d1d3eac0f0
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/nsWindowsDHCPClient.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIDHCPClient.h"
+#include "nsNetCID.h"
+#include "WindowsNetworkFunctionsWrapper.h"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+namespace windowsDHCPClient {
+
+class nsWindowsDHCPClient final : public nsIDHCPClient {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDHCPCLIENT
+
+ explicit nsWindowsDHCPClient(
+ WindowsNetworkFunctionsWrapper* aNetworkFunctions =
+ new WindowsNetworkFunctionsWrapper())
+ : mNetworkFunctions(aNetworkFunctions){};
+
+ private:
+ ~nsWindowsDHCPClient(){};
+ WindowsNetworkFunctionsWrapper* mNetworkFunctions;
+};
+
+} // namespace windowsDHCPClient
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
diff --git a/toolkit/system/windowsDHCPClient/tests/gtest/TestDHCPUtils.cpp b/toolkit/system/windowsDHCPClient/tests/gtest/TestDHCPUtils.cpp
new file mode 100644
index 0000000000..483e8663a8
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/tests/gtest/TestDHCPUtils.cpp
@@ -0,0 +1,306 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DHCPUtils.h"
+#include "gtest/gtest.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsWindowsDHCPClient.h"
+
+using namespace mozilla::toolkit::system::windowsDHCPClient;
+
+class WindowsNetworkFunctionsMock : public WindowsNetworkFunctionsWrapper {
+ public:
+ WindowsNetworkFunctionsMock() : mAddressesToReturn(nullptr) {
+ memset(mOptions, 0, sizeof(char*) * 256);
+ }
+
+ ULONG GetAdaptersAddressesWrapped(
+ _In_ ULONG Family, _In_ ULONG Flags, _In_ PVOID Reserved,
+ _Inout_ PIP_ADAPTER_ADDRESSES AdapterAddresses,
+ _Inout_ PULONG SizePointer) {
+ if (*SizePointer < sizeof(*mAddressesToReturn)) {
+ *SizePointer = sizeof(*mAddressesToReturn);
+ return ERROR_BUFFER_OVERFLOW;
+ }
+
+ *SizePointer = sizeof(*mAddressesToReturn);
+ memcpy(AdapterAddresses, mAddressesToReturn, *SizePointer);
+ return 0;
+ }
+
+ DWORD DhcpRequestParamsWrapped(_In_ DWORD Flags, _In_ LPVOID Reserved,
+ _In_ LPWSTR AdapterName,
+ _In_ LPDHCPCAPI_CLASSID ClassId,
+ _In_ DHCPCAPI_PARAMS_ARRAY SendParams,
+ _Inout_ DHCPCAPI_PARAMS_ARRAY RecdParams,
+ _In_ LPBYTE Buffer, _Inout_ LPDWORD pSize,
+ _In_ LPWSTR RequestIdStr) {
+ mLastRequestedNetworkAdapterName.Assign(AdapterName);
+
+ if (mOptions[RecdParams.Params[0].OptionId] == nullptr) {
+ RecdParams.Params[0].nBytesData = 0;
+ } else {
+ RecdParams.Params[0].Data = Buffer;
+ size_t lengthOfValue = strlen(mOptions[RecdParams.Params[0].OptionId]);
+ if (*pSize > lengthOfValue) {
+ memcpy(Buffer, mOptions[RecdParams.Params[0].OptionId], lengthOfValue);
+ RecdParams.Params[0].nBytesData = lengthOfValue;
+ } else {
+ *pSize = lengthOfValue;
+ return ERROR_MORE_DATA;
+ }
+ }
+ return 0;
+ }
+
+ void AddAdapterAddresses(IP_ADAPTER_ADDRESSES& aAddressesToAdd) {
+ if (mAddressesToReturn == nullptr) {
+ mAddressesToReturn = &aAddressesToAdd;
+ return;
+ }
+ IP_ADAPTER_ADDRESSES* tail = mAddressesToReturn;
+
+ while (tail->Next != nullptr) {
+ tail = tail->Next;
+ }
+ tail->Next = &aAddressesToAdd;
+ }
+
+ void SetDHCPOption(uint8_t option, const char* value) {
+ mOptions[option] = value;
+ }
+
+ nsString GetLastRequestedNetworkAdapterName() {
+ return mLastRequestedNetworkAdapterName;
+ }
+
+ private:
+ IP_ADAPTER_ADDRESSES* mAddressesToReturn = nullptr;
+ const char* mOptions[256];
+ nsString mLastRequestedNetworkAdapterName;
+};
+
+class TestDHCPUtils : public ::testing::Test {
+ protected:
+ RefPtr<WindowsNetworkFunctionsMock> mMockWindowsFunctions;
+ nsCString mDefaultAdapterName;
+
+ virtual void SetUp() {
+ mMockWindowsFunctions = new WindowsNetworkFunctionsMock();
+ mDefaultAdapterName.AssignLiteral("my favourite network adapter");
+ }
+
+ void Given_DHCP_Option_Is(uint8_t option, const char* value) {
+ mMockWindowsFunctions.get()->SetDHCPOption(option, value);
+ }
+
+ void Given_Network_Adapter_Called(IP_ADAPTER_ADDRESSES& adapterAddresses,
+ const char* adapterName) {
+ adapterAddresses.AdapterName = const_cast<char*>(adapterName);
+ adapterAddresses.Next = nullptr;
+ adapterAddresses.Dhcpv4Server.iSockaddrLength = 0;
+ adapterAddresses.Dhcpv6Server.iSockaddrLength = 0;
+ AddAdapterAddresses(adapterAddresses);
+ }
+
+ void Given_Network_Adapter_Supports_DHCP_V4(
+ IP_ADAPTER_ADDRESSES& adapterAddresses) {
+ adapterAddresses.Dhcpv4Server.iSockaddrLength = 4;
+ }
+
+ void Given_Network_Adapter_Supports_DHCP_V6(
+ IP_ADAPTER_ADDRESSES& adapterAddresses) {
+ adapterAddresses.Dhcpv6Server.iSockaddrLength = 12;
+ }
+
+ void Given_Network_Adapter_Has_Operational_Status(
+ IP_ADAPTER_ADDRESSES& adapterAddresses, IF_OPER_STATUS operStatus) {
+ adapterAddresses.OperStatus = operStatus;
+ }
+
+ private:
+ void AddAdapterAddresses(IP_ADAPTER_ADDRESSES& aAddressToAdd) {
+ mMockWindowsFunctions.get()->AddAdapterAddresses(aAddressToAdd);
+ }
+};
+
+// following class currently just distinguishes tests of nsWindowsDHCPClient
+// from tests of DHCPUtils.
+class TestNsWindowsDHCPClient : public TestDHCPUtils {};
+
+TEST_F(TestDHCPUtils, TestGetAdaptersAddresses) {
+ IP_ADAPTER_ADDRESSES adapterAddresses = {};
+ Given_Network_Adapter_Called(adapterAddresses,
+ "my favourite network adapter");
+ Given_Network_Adapter_Supports_DHCP_V4(adapterAddresses);
+ Given_Network_Adapter_Has_Operational_Status(adapterAddresses,
+ IfOperStatusUp);
+
+ nsCString networkAdapterName;
+
+ ASSERT_EQ(NS_OK, GetActiveDHCPNetworkAdapterName(networkAdapterName,
+ mMockWindowsFunctions));
+
+ ASSERT_STREQ(networkAdapterName.Data(), "my favourite network adapter");
+}
+
+TEST_F(TestDHCPUtils, TestGetAdaptersAddressesNoAvailableNetworks) {
+ IP_ADAPTER_ADDRESSES adapterAddresses = {};
+ Given_Network_Adapter_Called(adapterAddresses,
+ "my favourite network adapter");
+ Given_Network_Adapter_Supports_DHCP_V4(adapterAddresses);
+ Given_Network_Adapter_Has_Operational_Status(adapterAddresses,
+ IfOperStatusDown);
+
+ nsCString networkAdapterName;
+ ASSERT_EQ(NS_ERROR_NOT_AVAILABLE,
+ GetActiveDHCPNetworkAdapterName(networkAdapterName,
+ mMockWindowsFunctions));
+
+ ASSERT_STREQ(networkAdapterName.Data(), "");
+}
+
+TEST_F(TestDHCPUtils, TestGetAdaptersAddressesNoNetworksWithDHCP) {
+ IP_ADAPTER_ADDRESSES adapterAddresses = {};
+ Given_Network_Adapter_Called(adapterAddresses,
+ "my favourite network adapter");
+ Given_Network_Adapter_Has_Operational_Status(adapterAddresses,
+ IfOperStatusUp);
+
+ nsCString networkAdapterName;
+ ASSERT_EQ(NS_ERROR_NOT_AVAILABLE,
+ GetActiveDHCPNetworkAdapterName(networkAdapterName,
+ mMockWindowsFunctions));
+
+ ASSERT_STREQ(networkAdapterName.Data(), "");
+}
+
+TEST_F(TestDHCPUtils, TestGetAdaptersAddressesSecondNetworkIsAvailable) {
+ IP_ADAPTER_ADDRESSES adapterAddresses = {};
+ Given_Network_Adapter_Called(adapterAddresses,
+ "my favourite network adapter");
+ Given_Network_Adapter_Supports_DHCP_V4(adapterAddresses);
+ Given_Network_Adapter_Has_Operational_Status(adapterAddresses,
+ IfOperStatusDown);
+
+ IP_ADAPTER_ADDRESSES secondAdapterAddresses = {};
+ Given_Network_Adapter_Called(secondAdapterAddresses,
+ "my second favourite network adapter");
+ Given_Network_Adapter_Supports_DHCP_V6(secondAdapterAddresses);
+ Given_Network_Adapter_Has_Operational_Status(secondAdapterAddresses,
+ IfOperStatusUp);
+
+ nsCString networkAdapterName;
+ ASSERT_EQ(NS_OK, GetActiveDHCPNetworkAdapterName(networkAdapterName,
+ mMockWindowsFunctions));
+
+ ASSERT_STREQ(networkAdapterName.Data(),
+ "my second favourite network adapter");
+}
+
+TEST_F(TestDHCPUtils, TestGetOption) {
+ const char* pacURL = "http://pac.com";
+ Given_DHCP_Option_Is(1, "My network option");
+ Given_DHCP_Option_Is(252, pacURL);
+
+ std::vector<char> optionValue(255, *"originalValue originalValue");
+ memcpy(optionValue.data(), "originalValue originalValue",
+ strlen("originalValue originalValue") + 1);
+
+ uint32_t size = 255;
+
+ nsresult retVal = RetrieveOption(mDefaultAdapterName, 252, optionValue, &size,
+ mMockWindowsFunctions);
+
+ ASSERT_EQ(strlen(pacURL), size);
+ ASSERT_STREQ("http://pac.comoriginalValue", optionValue.data());
+ ASSERT_EQ(NS_OK, retVal);
+}
+
+TEST_F(TestDHCPUtils, TestGetAbsentOption) {
+ std::vector<char> optionValue(255);
+ uint32_t size = 256;
+ memcpy(optionValue.data(), "originalValue", strlen("originalValue") + 1);
+
+ nsresult retVal = RetrieveOption(mDefaultAdapterName, 252, optionValue, &size,
+ mMockWindowsFunctions);
+
+ ASSERT_EQ(0U, size);
+ ASSERT_EQ(NS_ERROR_NOT_AVAILABLE, retVal);
+}
+
+TEST_F(TestDHCPUtils, TestGetTooLongOption) {
+ Given_DHCP_Option_Is(252, "http://pac.com");
+
+ std::vector<char> optionValue(255);
+ memcpy(optionValue.data(), "originalValue", strlen("originalValue") + 1);
+ uint32_t size = 4;
+ nsresult retVal = RetrieveOption(mDefaultAdapterName, 252, optionValue, &size,
+ mMockWindowsFunctions);
+
+ ASSERT_STREQ("httpinalValue", optionValue.data());
+ ASSERT_EQ(NS_ERROR_LOSS_OF_SIGNIFICANT_DATA, retVal);
+ ASSERT_EQ(strlen("http://pac.com"), size);
+}
+
+TEST_F(TestNsWindowsDHCPClient, TestGettingOptionThroughNSWindowsDHCPClient) {
+ IP_ADAPTER_ADDRESSES adapterAddresses = {};
+ Given_Network_Adapter_Called(adapterAddresses,
+ "my favourite network adapter");
+ Given_Network_Adapter_Supports_DHCP_V4(adapterAddresses);
+ Given_Network_Adapter_Has_Operational_Status(adapterAddresses,
+ IfOperStatusUp);
+ Given_DHCP_Option_Is(252, "http://pac.com");
+
+ nsCString optionValue;
+ nsCOMPtr<nsIDHCPClient> dhcpClient =
+ new nsWindowsDHCPClient(mMockWindowsFunctions);
+ nsresult retVal = dhcpClient->GetOption(252, optionValue);
+
+ ASSERT_STREQ("http://pac.com", optionValue.Data());
+ ASSERT_STREQ(
+ L"my favourite network adapter",
+ mMockWindowsFunctions->GetLastRequestedNetworkAdapterName().Data());
+ ASSERT_EQ(NS_OK, retVal);
+}
+
+TEST_F(
+ TestNsWindowsDHCPClient,
+ TestGettingOptionThroughNSWindowsDHCPClientWhenNoAvailableNetworkAdapter) {
+ IP_ADAPTER_ADDRESSES adapterAddresses = {};
+ Given_Network_Adapter_Called(adapterAddresses,
+ "my favourite network adapter");
+ Given_Network_Adapter_Has_Operational_Status(adapterAddresses,
+ IfOperStatusDown);
+ Given_DHCP_Option_Is(252, "http://pac.com");
+
+ nsCString optionValue;
+ nsCOMPtr<nsIDHCPClient> dhcpClient =
+ new nsWindowsDHCPClient(mMockWindowsFunctions);
+ nsresult retVal = dhcpClient->GetOption(252, optionValue);
+
+ ASSERT_STREQ("", optionValue.Data());
+ ASSERT_EQ(NS_ERROR_NOT_AVAILABLE, retVal);
+}
+
+TEST_F(TestNsWindowsDHCPClient,
+ TestGettingAbsentOptionThroughNSWindowsDHCPClient) {
+ IP_ADAPTER_ADDRESSES adapterAddresses = {};
+ Given_Network_Adapter_Called(adapterAddresses,
+ "my favourite network adapter");
+ Given_Network_Adapter_Supports_DHCP_V6(adapterAddresses);
+ Given_Network_Adapter_Has_Operational_Status(adapterAddresses,
+ IfOperStatusUp);
+
+ nsCString optionValue;
+ nsCOMPtr<nsIDHCPClient> dhcpClient =
+ new nsWindowsDHCPClient(mMockWindowsFunctions);
+ nsresult retVal = dhcpClient->GetOption(252, optionValue);
+
+ ASSERT_STREQ("", optionValue.Data());
+ ASSERT_EQ(NS_ERROR_NOT_AVAILABLE, retVal);
+}
diff --git a/toolkit/system/windowsDHCPClient/tests/gtest/moz.build b/toolkit/system/windowsDHCPClient/tests/gtest/moz.build
new file mode 100644
index 0000000000..b87007a353
--- /dev/null
+++ b/toolkit/system/windowsDHCPClient/tests/gtest/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking: HTTP")
+
+UNIFIED_SOURCES += [
+ "TestDHCPUtils.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/toolkit/system/windowsDHCPClient",
+]
+
+FINAL_LIBRARY = "xul-gtest"
+
+if CONFIG["GNU_CXX"]:
+ CXXFLAGS += ["-Wshadow"]
diff --git a/toolkit/system/windowsPackageManager/components.conf b/toolkit/system/windowsPackageManager/components.conf
new file mode 100644
index 0000000000..a1bab60e03
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/components.conf
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{c75da378-521f-11ec-84cc-336cd3921c24}',
+ 'contract_ids': ['@mozilla.org/windows-package-manager;1'],
+ 'type': 'mozilla::toolkit::system::nsWindowsPackageManager',
+ 'headers': ['/toolkit/system/windowsPackageManager/nsWindowsPackageManager.h']
+ },
+]
diff --git a/toolkit/system/windowsPackageManager/moz.build b/toolkit/system/windowsPackageManager/moz.build
new file mode 100644
index 0000000000..13f940edbe
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/moz.build
@@ -0,0 +1,26 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Firefox", "Installer")
+
+TEST_DIRS += ["tests/gtest"]
+
+SOURCES += ["nsWindowsPackageManager.cpp"]
+
+LOCAL_INCLUDES += ["/toolkit/components/jsoncpp/include"]
+
+USE_LIBS += ["jsoncpp"]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+XPIDL_MODULE = "windows_package_manager"
+
+XPIDL_SOURCES += ["nsIWindowsPackageManager.idl"]
+
+FINAL_LIBRARY = "xul"
diff --git a/toolkit/system/windowsPackageManager/nsIWindowsPackageManager.idl b/toolkit/system/windowsPackageManager/nsIWindowsPackageManager.idl
new file mode 100644
index 0000000000..f22970abe2
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/nsIWindowsPackageManager.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(ad57ac40-52f0-11ec-ada8-4f671255c4aa)]
+interface nsIWindowsPackageManager : nsISupports
+{
+/* Searches for any user installed MSIX packages whose
+ * packageFamilyName matches any of the provided `aNamePrefixes`
+ * and returns them. The Windows APIs only allow querying user
+ * installed packages without elevation, so this will not see any
+ * packages installed by another user.
+ */
+ Array<AString> findUserInstalledPackages(in Array<AString> prefix);
+
+/* When running within a Packaged App environment, returns the
+ * InstalledDate of the Package. If called when not running within
+ * a Packaged App environment, throws NS_ERROR_NOT_IMPLEMENTED.
+ * Any other others will cause NS_ERROR_FAILURE to be thrown.
+ */
+ unsigned long long getInstalledDate();
+
+/* Asynchronously retrieves the campaignId, if any, a user's Microsoft Store install is
+ * associated with. These are present if the user clicked a "ms-window-store://"
+ * or "https://" link that included a "cid" query argument the very first time
+ * they installed the app. (This value appears to be cached forever, so
+ * subsequent installs will not refresh it.) If a non-empty campaign ID is
+ * found it will be assumed to be a properly formatted attribution code and
+ * have an additional "msstoresignedin" key appended to it indicate whether or
+ * not the user was signed in when they installed the application. This key
+ * will either be set to "true" or "false".
+ *
+ * @throw NS_ERROR_NOT_IMPLEMENTED if called on Windows 8 or earlier, or from
+ * a non-packaged build.
+ * @throw NS_ERROR_FAILURE for any other errors
+ */
+ [implicit_jscontext]
+ Promise campaignId();
+};
diff --git a/toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp b/toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp
new file mode 100644
index 0000000000..2c2d3ffbc5
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp
@@ -0,0 +1,462 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWindowsPackageManager.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/Logging.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#ifndef __MINGW32__
+# include "nsProxyRelease.h"
+# include <comutil.h>
+# include <wrl.h>
+# include <windows.applicationmodel.store.h>
+# include <windows.management.deployment.h>
+# include <windows.services.store.h>
+#endif // __MINGW32__
+#include "nsError.h"
+#include "nsString.h"
+#include "nsISupportsPrimitives.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsXPCOMCID.h"
+#include "json/json.h"
+
+#ifndef __MINGW32__ // WinRT headers not yet supported by MinGW
+using namespace Microsoft::WRL;
+using namespace Microsoft::WRL::Wrappers;
+using namespace ABI::Windows;
+using namespace ABI::Windows::Foundation;
+using namespace ABI::Windows::Management;
+using namespace ABI::Windows::Services::Store;
+#endif
+
+// Campaign IDs are stored in a JSON data structure under this key
+// for installs done without the user being signed in to the Microsoft
+// store.
+#define CAMPAIGN_ID_JSON_FIELD_NAME "customPolicyField1"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+
+NS_IMPL_ISUPPORTS(nsWindowsPackageManager, nsIWindowsPackageManager)
+
+NS_IMETHODIMP
+nsWindowsPackageManager::FindUserInstalledPackages(
+ const nsTArray<nsString>& aNamePrefixes, nsTArray<nsString>& aPackages) {
+#ifdef __MINGW32__
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ ComPtr<IInspectable> pmInspectable;
+ ComPtr<Deployment::IPackageManager> pm;
+ HRESULT hr = RoActivateInstance(
+ HStringReference(
+ RuntimeClass_Windows_Management_Deployment_PackageManager)
+ .Get(),
+ &pmInspectable);
+ if (!SUCCEEDED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+ hr = pmInspectable.As(&pm);
+ if (!SUCCEEDED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ComPtr<Collections::IIterable<ApplicationModel::Package*> > pkgs;
+ hr = pm->FindPackagesByUserSecurityId(NULL, &pkgs);
+ if (!SUCCEEDED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+ ComPtr<Collections::IIterator<ApplicationModel::Package*> > iterator;
+ hr = pkgs->First(&iterator);
+ if (!SUCCEEDED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+ boolean hasCurrent;
+ hr = iterator->get_HasCurrent(&hasCurrent);
+ while (SUCCEEDED(hr) && hasCurrent) {
+ ComPtr<ApplicationModel::IPackage> package;
+ hr = iterator->get_Current(&package);
+ if (!SUCCEEDED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+ ComPtr<ApplicationModel::IPackageId> packageId;
+ hr = package->get_Id(&packageId);
+ if (!SUCCEEDED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+ HString rawName;
+ hr = packageId->get_FamilyName(rawName.GetAddressOf());
+ if (!SUCCEEDED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+ unsigned int tmp;
+ nsString name(rawName.GetRawBuffer(&tmp));
+ for (uint32_t i = 0; i < aNamePrefixes.Length(); i++) {
+ if (name.Find(aNamePrefixes.ElementAt(i)) != kNotFound) {
+ aPackages.AppendElement(name);
+ break;
+ }
+ }
+ hr = iterator->MoveNext(&hasCurrent);
+ }
+ return NS_OK;
+#endif // __MINGW32__
+}
+
+NS_IMETHODIMP
+nsWindowsPackageManager::GetInstalledDate(uint64_t* ts) {
+#ifdef __MINGW32__
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+ ComPtr<ApplicationModel::IPackageStatics> pkgStatics;
+ HRESULT hr = RoGetActivationFactory(
+ HStringReference(RuntimeClass_Windows_ApplicationModel_Package).Get(),
+ IID_PPV_ARGS(&pkgStatics));
+ if (!SUCCEEDED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ComPtr<ApplicationModel::IPackage> package;
+ hr = pkgStatics->get_Current(&package);
+ if (!SUCCEEDED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ComPtr<ApplicationModel::IPackage3> package3;
+ hr = package.As(&package3);
+ if (!SUCCEEDED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DateTime installedDate;
+ hr = package3->get_InstalledDate(&installedDate);
+ if (!SUCCEEDED(hr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *ts = installedDate.UniversalTime;
+ return NS_OK;
+#endif // __MINGW32__
+}
+
+static HRESULT RejectOnMainThread(
+ const char* aName, nsMainThreadPtrHandle<dom::Promise> promiseHolder,
+ nsresult result) {
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
+ aName, [promiseHolder = std::move(promiseHolder), result]() {
+ promiseHolder.get()->MaybeReject(result);
+ }));
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+
+ return S_OK;
+}
+
+#ifndef __MINGW32__
+// forward declarations
+static void GetCampaignIdFromStoreProductOnBackgroundThread(
+ ComPtr<IAsyncOperation<StoreProductResult*> > asyncSpr,
+ ComPtr<IStoreContext> storeContext,
+ nsMainThreadPtrHandle<dom::Promise> promiseHolder);
+static void GetCampaignIdFromLicenseOnBackgroundThread(
+ ComPtr<IAsyncOperation<StoreAppLicense*> > asyncSal,
+ nsMainThreadPtrHandle<dom::Promise> promiseHolder,
+ nsAutoString aCampaignId);
+#endif // __MINGW32__
+
+static std::tuple<nsMainThreadPtrHolder<dom::Promise>*, nsresult>
+InitializePromise(JSContext* aCx, dom::Promise** aPromise) {
+ *aPromise = nullptr;
+
+ nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!global)) {
+ return std::make_tuple(nullptr, NS_ERROR_FAILURE);
+ }
+
+ ErrorResult erv;
+ RefPtr<dom::Promise> outer = dom::Promise::Create(global, erv);
+ if (NS_WARN_IF(erv.Failed())) {
+ return std::make_tuple(nullptr, erv.StealNSResult());
+ }
+
+ auto promiseHolder = new nsMainThreadPtrHolder<dom::Promise>(
+ "nsWindowsPackageManager::CampaignId Promise", outer);
+
+ outer.forget(aPromise);
+
+ return std::make_tuple(promiseHolder, NS_OK);
+}
+
+NS_IMETHODIMP
+nsWindowsPackageManager::CampaignId(JSContext* aCx, dom::Promise** aPromise) {
+ NS_ENSURE_ARG_POINTER(aPromise);
+
+#ifdef __MINGW32__
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else
+
+ // This is only relevant for MSIX packaged builds.
+ if (!mozilla::HasPackageIdentity()) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ auto [unwrappedPromiseHolder, result] = InitializePromise(aCx, aPromise);
+
+ NS_ENSURE_SUCCESS(result, result);
+
+ nsMainThreadPtrHandle<dom::Promise> promiseHolder(unwrappedPromiseHolder);
+
+ ComPtr<IStoreContextStatics> scStatics = nullptr;
+ HRESULT hr = RoGetActivationFactory(
+ HStringReference(RuntimeClass_Windows_Services_Store_StoreContext).Get(),
+ IID_PPV_ARGS(&scStatics));
+ /* Per
+ * https://docs.microsoft.com/en-us/windows/uwp/publish/create-a-custom-app-promotion-campaign#programmatically-retrieve-the-custom-campaign-id-for-an-app
+ * there are three ways to find a campaign ID.
+ * 1) If the user was logged into the Microsoft Store when they installed
+ * it will be available in a SKU.
+ * 2) If they were not logged in, it will be available through the
+ * StoreAppLicense
+ *
+ * There's also a third way, in theory, to retrieve it on very old versions of
+ * Windows 10 (1511 or earlier). However, these versions don't appear to be
+ * able to use the Windows Store at all anymore - so there's no point in
+ * supporting that scenario.
+ *
+ */
+ if (!SUCCEEDED(hr) || scStatics == nullptr) {
+ promiseHolder.get()->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ ComPtr<IStoreContext> storeContext = nullptr;
+ hr = scStatics->GetDefault(&storeContext);
+ if (!SUCCEEDED(hr) || storeContext == nullptr) {
+ promiseHolder.get()->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ /* Despite the documentation not saying otherwise, these don't work
+ * consistently when called from the main thread. I tried the two scenarios
+ * described above multiple times, and couldn't consistently get the campaign
+ * id when running this code async on the main thread. So instead, this
+ * dispatches to a background task to do the work, and then dispatches to the
+ * main thread to call back into the Javascript asynchronously
+ *
+ */
+ result = NS_DispatchBackgroundTask(
+ NS_NewRunnableFunction(
+ __func__,
+ [storeContext = std::move(storeContext),
+ promiseHolder = std::move(promiseHolder)]() -> void {
+ ComPtr<IAsyncOperation<StoreProductResult*> > asyncSpr = nullptr;
+
+ auto hr =
+ storeContext->GetStoreProductForCurrentAppAsync(&asyncSpr);
+ if (!SUCCEEDED(hr) || asyncSpr == nullptr) {
+ RejectOnMainThread(__func__, std::move(promiseHolder),
+ NS_ERROR_FAILURE);
+ return;
+ }
+
+ auto callback =
+ Callback<IAsyncOperationCompletedHandler<StoreProductResult*> >(
+ [promiseHolder, asyncSpr,
+ storeContext = std::move(storeContext)](
+ IAsyncOperation<StoreProductResult*>* asyncInfo,
+ AsyncStatus status) -> HRESULT {
+ bool asyncOpSucceeded = status == AsyncStatus::Completed;
+ if (!asyncOpSucceeded) {
+ return RejectOnMainThread(__func__,
+ std::move(promiseHolder),
+ NS_ERROR_FAILURE);
+ }
+
+ GetCampaignIdFromStoreProductOnBackgroundThread(
+ std::move(asyncSpr), std::move(storeContext),
+ std::move(promiseHolder));
+
+ return S_OK;
+ });
+
+ hr = asyncSpr->put_Completed(callback.Get());
+ if (!SUCCEEDED(hr)) {
+ RejectOnMainThread(__func__, std::move(promiseHolder),
+ NS_ERROR_FAILURE);
+ return;
+ }
+ }),
+ NS_DISPATCH_EVENT_MAY_BLOCK);
+
+ if (NS_FAILED(result)) {
+ promiseHolder.get()->MaybeReject(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ return NS_OK;
+#endif // __MINGW32__
+}
+
+#ifndef __MINGW32__
+static void GetCampaignIdFromStoreProductOnBackgroundThread(
+ ComPtr<IAsyncOperation<StoreProductResult*> > asyncSpr,
+ ComPtr<IStoreContext> storeContext,
+ nsMainThreadPtrHandle<dom::Promise> promiseHolder) {
+ ComPtr<IStoreProductResult> productResult = nullptr;
+
+ auto hr = asyncSpr->GetResults(&productResult);
+ if (!SUCCEEDED(hr) || productResult == nullptr) {
+ RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsAutoString campaignId;
+
+ ComPtr<IStoreProduct> product = nullptr;
+ hr = productResult->get_Product(&product);
+ if (SUCCEEDED(hr) && (product != nullptr)) {
+ ComPtr<Collections::IVectorView<StoreSku*> > skus = nullptr;
+ hr = product->get_Skus(&skus);
+ if (!SUCCEEDED(hr) || skus == nullptr) {
+ RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE);
+ return;
+ }
+
+ unsigned int size;
+ hr = skus->get_Size(&size);
+ if (!SUCCEEDED(hr)) {
+ RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE);
+ return;
+ }
+
+ for (unsigned int i = 0; i < size; i++) {
+ ComPtr<IStoreSku> sku = nullptr;
+ hr = skus->GetAt(i, &sku);
+ if (!SUCCEEDED(hr) || sku == nullptr) {
+ RejectOnMainThread(__func__, std::move(promiseHolder),
+ NS_ERROR_FAILURE);
+ return;
+ }
+
+ boolean isInUserCollection = false;
+ hr = sku->get_IsInUserCollection(&isInUserCollection);
+ if (!SUCCEEDED(hr) || !isInUserCollection) continue;
+
+ ComPtr<IStoreCollectionData> scd = nullptr;
+ hr = sku->get_CollectionData(&scd);
+ if (!SUCCEEDED(hr) || scd == nullptr) continue;
+
+ HString msCampaignId;
+ hr = scd->get_CampaignId(msCampaignId.GetAddressOf());
+ if (!SUCCEEDED(hr)) continue;
+
+ unsigned int tmp;
+ campaignId.Assign(msCampaignId.GetRawBuffer(&tmp));
+ if (campaignId.Length() > 0) {
+ campaignId.AppendLiteral("&msstoresignedin=true");
+ }
+ }
+ }
+
+ if (!campaignId.IsEmpty()) {
+ // If we got here, it means that campaignId has been processed and can be
+ // sent back via the promise
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
+ __func__, [promiseHolder = std::move(promiseHolder),
+ campaignId = std::move(campaignId)]() {
+ promiseHolder.get()->MaybeResolve(campaignId);
+ }));
+
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+
+ return;
+ }
+
+ // There's various points above that could exit without a failure.
+ // If we get here without a campaignId we may as well just check
+ // the AppStoreLicense.
+ ComPtr<IAsyncOperation<StoreAppLicense*> > asyncSal = nullptr;
+
+ hr = storeContext->GetAppLicenseAsync(&asyncSal);
+ if (!SUCCEEDED(hr) || asyncSal == nullptr) {
+ RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE);
+ return;
+ }
+
+ auto callback = Callback<IAsyncOperationCompletedHandler<StoreAppLicense*> >(
+ [asyncSal, promiseHolder, campaignId = std::move(campaignId)](
+ IAsyncOperation<StoreAppLicense*>* asyncInfo,
+ AsyncStatus status) -> HRESULT {
+ bool asyncOpSucceeded = status == AsyncStatus::Completed;
+ if (!asyncOpSucceeded) {
+ return RejectOnMainThread(__func__, std::move(promiseHolder),
+ NS_ERROR_FAILURE);
+ }
+
+ GetCampaignIdFromLicenseOnBackgroundThread(std::move(asyncSal),
+ std::move(promiseHolder),
+ std::move(campaignId));
+
+ return S_OK;
+ });
+
+ hr = asyncSal->put_Completed(callback.Get());
+ if (!SUCCEEDED(hr)) {
+ RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE);
+ return;
+ }
+}
+
+static void GetCampaignIdFromLicenseOnBackgroundThread(
+ ComPtr<IAsyncOperation<StoreAppLicense*> > asyncSal,
+ nsMainThreadPtrHandle<dom::Promise> promiseHolder,
+ nsAutoString aCampaignId) {
+ ComPtr<IStoreAppLicense> license = nullptr;
+ auto hr = asyncSal->GetResults(&license);
+ if (!SUCCEEDED(hr) || license == nullptr) {
+ RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE);
+ return;
+ }
+
+ HString extendedData;
+ hr = license->get_ExtendedJsonData(extendedData.GetAddressOf());
+ if (!SUCCEEDED(hr)) {
+ RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE);
+ return;
+ }
+
+ Json::Value jsonData;
+ Json::Reader jsonReader;
+
+ unsigned int tmp;
+ nsAutoString key(extendedData.GetRawBuffer(&tmp));
+ if (!jsonReader.parse(NS_ConvertUTF16toUTF8(key).get(), jsonData, false)) {
+ RejectOnMainThread(__func__, std::move(promiseHolder), NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (jsonData.isMember(CAMPAIGN_ID_JSON_FIELD_NAME) &&
+ jsonData[CAMPAIGN_ID_JSON_FIELD_NAME].isString()) {
+ aCampaignId.Assign(
+ NS_ConvertUTF8toUTF16(
+ jsonData[CAMPAIGN_ID_JSON_FIELD_NAME].asString().c_str())
+ .get());
+ if (aCampaignId.Length() > 0) {
+ aCampaignId.AppendLiteral("&msstoresignedin=false");
+ }
+ }
+
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
+ __func__, [promiseHolder = std::move(promiseHolder),
+ aCampaignId = std::move(aCampaignId)]() {
+ promiseHolder.get()->MaybeResolve(aCampaignId);
+ }));
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread failed");
+}
+#endif // __MINGW32__
+
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
diff --git a/toolkit/system/windowsPackageManager/nsWindowsPackageManager.h b/toolkit/system/windowsPackageManager/nsWindowsPackageManager.h
new file mode 100644
index 0000000000..30cc8a288e
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/nsWindowsPackageManager.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_toolkit_system_nsWindowsPackageManager_h
+#define mozilla_toolkit_system_nsWindowsPackageManager_h
+
+#include "nsIWindowsPackageManager.h"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+
+class nsWindowsPackageManager final : public nsIWindowsPackageManager {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWINDOWSPACKAGEMANAGER
+
+ private:
+ ~nsWindowsPackageManager(){};
+};
+
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
+
+#endif // mozilla_toolkit_system_nsWindowsPackageManager_h
diff --git a/toolkit/system/windowsPackageManager/tests/gtest/TestWindowsPackageManager.cpp b/toolkit/system/windowsPackageManager/tests/gtest/TestWindowsPackageManager.cpp
new file mode 100644
index 0000000000..235f44a2f7
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/tests/gtest/TestWindowsPackageManager.cpp
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "nsWindowsPackageManager.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla::toolkit::system;
+
+TEST(WindowsPackageManager, TestWithMatches)
+{
+ nsCOMPtr<nsIWindowsPackageManager> wpm(
+ do_GetService("@mozilla.org/windows-package-manager;1"));
+ nsTArray<nsString> prefixes, packages;
+ // We're assuming that there will always be at least _one_ Microsoft package
+ // installed when we run tests. This will _probably_ hold true.
+ prefixes.AppendElement(u"Microsoft"_ns);
+ nsresult retVal = wpm->FindUserInstalledPackages(prefixes, packages);
+ ASSERT_GT(packages.Length(), 0U);
+ ASSERT_EQ(NS_OK, retVal);
+}
+
+TEST(WindowsPackageManager, TestWithoutMatches)
+{
+ nsCOMPtr<nsIWindowsPackageManager> wpm(
+ do_GetService("@mozilla.org/windows-package-manager;1"));
+ nsTArray<nsString> prefixes, packages;
+ prefixes.AppendElement(u"DoesntExist"_ns);
+ nsresult retVal = wpm->FindUserInstalledPackages(prefixes, packages);
+ ASSERT_EQ(packages.Length(), 0U);
+ ASSERT_EQ(NS_OK, retVal);
+}
diff --git a/toolkit/system/windowsPackageManager/tests/gtest/moz.build b/toolkit/system/windowsPackageManager/tests/gtest/moz.build
new file mode 100644
index 0000000000..244ae2dfaa
--- /dev/null
+++ b/toolkit/system/windowsPackageManager/tests/gtest/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ "TestWindowsPackageManager.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/toolkit/system/windowsPackageManager",
+]
+
+FINAL_LIBRARY = "xul-gtest"
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wshadow"]
diff --git a/toolkit/system/windowsproxy/components.conf b/toolkit/system/windowsproxy/components.conf
new file mode 100644
index 0000000000..4c8c62fc43
--- /dev/null
+++ b/toolkit/system/windowsproxy/components.conf
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{4e22d3ea-aaa2-436e-ada4-9247de57d367}',
+ 'contract_ids': ['@mozilla.org/system-proxy-settings;1'],
+ 'type': 'nsWindowsSystemProxySettings',
+ },
+]
diff --git a/toolkit/system/windowsproxy/moz.build b/toolkit/system/windowsproxy/moz.build
new file mode 100644
index 0000000000..cb0e98cd3d
--- /dev/null
+++ b/toolkit/system/windowsproxy/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking")
+
+SOURCES += ["nsWindowsSystemProxySettings.cpp"]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "../commonproxy",
+ "/netwerk/base",
+]
diff --git a/toolkit/system/windowsproxy/nsWindowsSystemProxySettings.cpp b/toolkit/system/windowsproxy/nsWindowsSystemProxySettings.cpp
new file mode 100644
index 0000000000..7921cb60a2
--- /dev/null
+++ b/toolkit/system/windowsproxy/nsWindowsSystemProxySettings.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+#include <ras.h>
+#include <wininet.h>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "nsISystemProxySettings.h"
+#include "mozilla/Components.h"
+#include "mozilla/ProfilerLabels.h"
+#include "nsPrintfCString.h"
+#include "nsNetCID.h"
+#include "nsThreadUtils.h"
+#include "prnetdb.h"
+#include "ProxyUtils.h"
+
+class nsWindowsSystemProxySettings final : public nsISystemProxySettings {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISYSTEMPROXYSETTINGS
+
+ nsWindowsSystemProxySettings(){};
+
+ private:
+ ~nsWindowsSystemProxySettings(){};
+
+ bool MatchOverride(const nsACString& aHost);
+ bool PatternMatch(const nsACString& aHost, const nsACString& aOverride);
+};
+
+NS_IMPL_ISUPPORTS(nsWindowsSystemProxySettings, nsISystemProxySettings)
+
+NS_IMETHODIMP
+nsWindowsSystemProxySettings::GetMainThreadOnly(bool* aMainThreadOnly) {
+ // bug 1366133: if you change this to main thread only, please handle
+ // nsProtocolProxyService::Resolve_Internal carefully to avoid hang on main
+ // thread.
+ *aMainThreadOnly = false;
+ return NS_OK;
+}
+
+static void SetProxyResult(const char* aType, const nsACString& aHostPort,
+ nsACString& aResult) {
+ aResult.AssignASCII(aType);
+ aResult.Append(' ');
+ aResult.Append(aHostPort);
+}
+
+static void SetProxyResultDirect(nsACString& aResult) {
+ // For whatever reason, a proxy is not to be used.
+ aResult.AssignLiteral("DIRECT");
+}
+
+static nsresult ReadInternetOption(uint32_t aOption, uint32_t& aFlags,
+ nsAString& aValue) {
+ // Bug 1366133: InternetGetConnectedStateExW() may cause hangs
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ DWORD connFlags = 0;
+ WCHAR connName[RAS_MaxEntryName + 1];
+ MOZ_SEH_TRY {
+ InternetGetConnectedStateExW(&connFlags, connName,
+ mozilla::ArrayLength(connName), 0);
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { return NS_ERROR_FAILURE; }
+
+ INTERNET_PER_CONN_OPTIONW options[2];
+ options[0].dwOption = INTERNET_PER_CONN_FLAGS_UI;
+ options[1].dwOption = aOption;
+
+ INTERNET_PER_CONN_OPTION_LISTW list;
+ list.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LISTW);
+ list.pszConnection =
+ connFlags & INTERNET_CONNECTION_MODEM ? connName : nullptr;
+ list.dwOptionCount = mozilla::ArrayLength(options);
+ list.dwOptionError = 0;
+ list.pOptions = options;
+
+ unsigned long size = sizeof(INTERNET_PER_CONN_OPTION_LISTW);
+ if (!InternetQueryOptionW(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION,
+ &list, &size)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aFlags = options[0].Value.dwValue;
+ aValue.Assign(options[1].Value.pszValue);
+ GlobalFree(options[1].Value.pszValue);
+
+ return NS_OK;
+}
+
+bool nsWindowsSystemProxySettings::MatchOverride(const nsACString& aHost) {
+ nsresult rv;
+ uint32_t flags = 0;
+ nsAutoString buf;
+
+ rv = ReadInternetOption(INTERNET_PER_CONN_PROXY_BYPASS, flags, buf);
+ if (NS_FAILED(rv)) return false;
+
+ NS_ConvertUTF16toUTF8 cbuf(buf);
+
+ nsAutoCString host(aHost);
+ int32_t start = 0;
+ int32_t end = cbuf.Length();
+
+ // Windows formats its proxy override list in the form:
+ // server;server;server where 'server' is a server name pattern or IP
+ // address, or "<local>". "<local>" must be translated to
+ // "localhost;127.0.0.1".
+ // In a server name pattern, a '*' character matches any substring and
+ // all other characters must match themselves; the whole pattern must match
+ // the whole hostname.
+ while (true) {
+ int32_t delimiter = cbuf.FindCharInSet(" ;", start);
+ if (delimiter == -1) delimiter = end;
+
+ if (delimiter != start) {
+ const nsAutoCString override(Substring(cbuf, start, delimiter - start));
+ if (override.EqualsLiteral("<local>")) {
+ PRNetAddr addr;
+ bool isIpAddr = (PR_StringToNetAddr(host.get(), &addr) == PR_SUCCESS);
+
+ // Don't use proxy for local hosts (plain hostname, no dots)
+ if (!isIpAddr && !host.Contains('.')) {
+ return true;
+ }
+
+ if (host.EqualsLiteral("127.0.0.1") || host.EqualsLiteral("::1")) {
+ return true;
+ }
+ } else if (PatternMatch(host, override)) {
+ return true;
+ }
+ }
+
+ if (delimiter == end) break;
+ start = ++delimiter;
+ }
+
+ return false;
+}
+
+bool nsWindowsSystemProxySettings::PatternMatch(const nsACString& aHost,
+ const nsACString& aOverride) {
+ return mozilla::toolkit::system::IsHostProxyEntry(aHost, aOverride);
+}
+
+nsresult nsWindowsSystemProxySettings::GetPACURI(nsACString& aResult) {
+ AUTO_PROFILER_LABEL("nsWindowsSystemProxySettings::GetPACURI", OTHER);
+ nsresult rv;
+ uint32_t flags = 0;
+ nsAutoString buf;
+
+ rv = ReadInternetOption(INTERNET_PER_CONN_AUTOCONFIG_URL, flags, buf);
+ if (!(flags & PROXY_TYPE_AUTO_PROXY_URL)) {
+ aResult.Truncate();
+ return rv;
+ }
+
+ if (NS_SUCCEEDED(rv)) aResult = NS_ConvertUTF16toUTF8(buf);
+ return rv;
+}
+
+nsresult nsWindowsSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
+ const nsACString& aScheme,
+ const nsACString& aHost,
+ const int32_t aPort,
+ nsACString& aResult) {
+ nsresult rv;
+ uint32_t flags = 0;
+ nsAutoString buf;
+
+ rv = ReadInternetOption(INTERNET_PER_CONN_PROXY_SERVER, flags, buf);
+ if (NS_FAILED(rv) || !(flags & PROXY_TYPE_PROXY)) {
+ SetProxyResultDirect(aResult);
+ return NS_OK;
+ }
+
+ if (MatchOverride(aHost)) {
+ SetProxyResultDirect(aResult);
+ return NS_OK;
+ }
+
+ NS_ConvertUTF16toUTF8 cbuf(buf);
+
+ constexpr auto kSocksPrefix = "socks="_ns;
+ nsAutoCString prefix;
+ ToLowerCase(aScheme, prefix);
+
+ prefix.Append('=');
+
+ nsAutoCString specificProxy;
+ nsAutoCString defaultProxy;
+ nsAutoCString socksProxy;
+ int32_t start = 0;
+ int32_t end = cbuf.Length();
+
+ while (true) {
+ int32_t delimiter = cbuf.FindCharInSet(" ;", start);
+ if (delimiter == -1) delimiter = end;
+
+ if (delimiter != start) {
+ const nsAutoCString proxy(Substring(cbuf, start, delimiter - start));
+ if (proxy.FindChar('=') == -1) {
+ // If a proxy name is listed by itself, it is used as the
+ // default proxy for any protocols that do not have a specific
+ // proxy specified.
+ // (http://msdn.microsoft.com/en-us/library/aa383996%28VS.85%29.aspx)
+ defaultProxy = proxy;
+ } else if (proxy.Find(prefix) == 0) {
+ // To list a proxy for a specific protocol, the string must
+ // follow the format "<protocol>=<protocol>://<proxy_name>".
+ // (http://msdn.microsoft.com/en-us/library/aa383996%28VS.85%29.aspx)
+ specificProxy = Substring(proxy, prefix.Length());
+ break;
+ } else if (proxy.Find(kSocksPrefix) == 0) {
+ // SOCKS proxy.
+ socksProxy =
+ Substring(proxy, kSocksPrefix.Length()); // "socks=" length.
+ }
+ }
+
+ if (delimiter == end) break;
+ start = ++delimiter;
+ }
+
+ if (!specificProxy.IsEmpty())
+ SetProxyResult("PROXY", specificProxy,
+ aResult); // Protocol-specific proxy.
+ else if (!defaultProxy.IsEmpty())
+ SetProxyResult("PROXY", defaultProxy, aResult); // Default proxy.
+ else if (!socksProxy.IsEmpty())
+ SetProxyResult("SOCKS", socksProxy, aResult); // SOCKS proxy.
+ else
+ SetProxyResultDirect(aResult); // Direct connection.
+
+ return NS_OK;
+}
+
+NS_IMPL_COMPONENT_FACTORY(nsWindowsSystemProxySettings) {
+ return mozilla::MakeAndAddRef<nsWindowsSystemProxySettings>()
+ .downcast<nsISupports>();
+}