diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/system | |
parent | Initial commit. (diff) | |
download | firefox-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 '')
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>(); +} |