Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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 "WindowsInternetFunctionsWrapper.h"
#include "WinRegistry.h"
#include "mozilla/Logging.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs_network.h"
#include "nsINetworkLinkService.h"
#include "nsIObserverService.h"
#include "nsThreadUtils.h"
#include "nsXPCOM.h"
static mozilla::LazyLogModule gWinProxyLog("WindowsProxy");
#define LOG(args) MOZ_LOG(gWinProxyLog, mozilla::LogLevel::Debug, args)
namespace mozilla {
namespace toolkit {
namespace system {
class NetworkLinkObserver final : public nsIObserver {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
explicit NetworkLinkObserver(WindowsInternetFunctionsWrapper* aWrapper)
: mWrapper(aWrapper) {
LOG(("NetworkLinkObserver %p constructed (wrapper=%p)", this, aWrapper));
}
private:
~NetworkLinkObserver() { LOG(("NetworkLinkObserver %p destroyed", this)); }
mozilla::WeakPtr<WindowsInternetFunctionsWrapper> mWrapper;
};
NS_IMPL_ISUPPORTS(NetworkLinkObserver, nsIObserver)
NS_IMETHODIMP NetworkLinkObserver::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData) {
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
LOG(("NetworkLinkObserver %p: XPCOM shutdown, removing observers", this));
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, NS_NETWORK_LINK_TOPIC);
obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
}
RefPtr<WindowsInternetFunctionsWrapper> wrapper = mWrapper.get();
mWrapper = nullptr;
if (wrapper) {
wrapper->Shutdown();
}
return NS_OK;
}
if (!strcmp(aTopic, NS_NETWORK_LINK_TOPIC) && mWrapper) {
LOG(("Network link changed, invalidating connection and proxy cache"));
mWrapper->mConnCacheValid = false;
mWrapper->mCacheValid = false;
}
return NS_OK;
}
static bool GetConnectionState(DWORD& aConnFlags, WCHAR* aConnName,
size_t aConnNameLen) {
MOZ_SEH_TRY {
InternetGetConnectedStateExW(&aConnFlags, aConnName, aConnNameLen, 0);
return true;
}
MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { return false; }
}
WindowsInternetFunctionsWrapper::WindowsInternetFunctionsWrapper() {
LOG(("WindowsInternetFunctionsWrapper %p constructed", this));
}
void WindowsInternetFunctionsWrapper::Init() {
MOZ_ASSERT(NS_IsMainThread());
LOG(("WindowsInternetFunctionsWrapper %p Init()", this));
if (!StaticPrefs::network_proxy_detect_system_proxy_changes()) {
return;
}
using namespace mozilla::widget::WinRegistry;
Key key(HKEY_CURRENT_USER,
u"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"_ns,
KeyMode::Notify);
if (key) {
mKeyWatcher = MakeUnique<KeyWatcher>(
std::move(key), GetCurrentSerialEventTarget(), [self = RefPtr{this}] {
LOG(
("WindowsInternetFunctionsWrapper %p: registry change, "
"invalidating cache",
self.get()));
self->mCacheValid = false;
});
}
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (obs) {
mNetworkLinkObserver = new NetworkLinkObserver(this);
obs->AddObserver(mNetworkLinkObserver, NS_NETWORK_LINK_TOPIC, false);
obs->AddObserver(mNetworkLinkObserver, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
false);
}
}
void WindowsInternetFunctionsWrapper::Shutdown() { mKeyWatcher = nullptr; }
WindowsInternetFunctionsWrapper::~WindowsInternetFunctionsWrapper() {
LOG(("WindowsInternetFunctionsWrapper %p destroyed", this));
if (mNetworkLinkObserver) {
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
if (obs) {
obs->RemoveObserver(mNetworkLinkObserver, NS_NETWORK_LINK_TOPIC);
obs->RemoveObserver(mNetworkLinkObserver, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
}
}
}
nsresult WindowsInternetFunctionsWrapper::ReadAllOptionsLocked(
DWORD aConnFlags, const nsString& aConnName) {
INTERNET_PER_CONN_OPTIONW options[4];
options[0].dwOption = INTERNET_PER_CONN_FLAGS_UI;
options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
options[3].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;
INTERNET_PER_CONN_OPTION_LISTW list;
list.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LISTW);
list.pszConnection = aConnFlags & INTERNET_CONNECTION_MODEM
? const_cast<WCHAR*>(reinterpret_cast<const WCHAR*>(
static_cast<const char16_t*>(aConnName.get())))
: nullptr;
list.dwOptionCount = std::size(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)) {
LOG(("ReadAllOptionsLocked: InternetQueryOptionW failed"));
return NS_ERROR_FAILURE;
}
mCachedFlags = options[0].Value.dwValue;
LOG(("ReadAllOptionsLocked: flags=0x%x", mCachedFlags));
auto assignAndFree = [](nsString& aDest, WCHAR* aSrc, const char* aName) {
if (aSrc) {
aDest.Assign(aSrc);
GlobalFree(aSrc);
} else {
aDest.Truncate();
}
LOG((" %s=[%s]", aName, NS_ConvertUTF16toUTF8(aDest).get()));
};
assignAndFree(mCachedProxyServer, options[1].Value.pszValue, "ProxyServer");
assignAndFree(mCachedProxyBypass, options[2].Value.pszValue, "ProxyBypass");
assignAndFree(mCachedAutoConfigUrl, options[3].Value.pszValue,
"AutoConfigUrl");
return NS_OK;
}
nsresult WindowsInternetFunctionsWrapper::ReadInternetOption(
uint32_t aOption, uint32_t& aFlags, nsAString& aValue) {
// Bug 1366133: InternetGetConnectedStateExW() may cause hangs
MOZ_ASSERT(!NS_IsMainThread());
if (!mConnCacheValid) {
DWORD connFlags = 0;
WCHAR connName[RAS_MaxEntryName + 1];
if (!GetConnectionState(connFlags, connName, std::size(connName))) {
return NS_ERROR_FAILURE;
}
nsString currentConnName;
if (connFlags & INTERNET_CONNECTION_MODEM) {
currentConnName.Assign(connName);
}
if (currentConnName != mCachedConnName) {
LOG(
("ReadInternetOption: connection changed [%s] -> [%s], invalidating "
"cache",
NS_ConvertUTF16toUTF8(mCachedConnName).get(),
NS_ConvertUTF16toUTF8(currentConnName).get()));
mCacheValid = false;
}
mCachedConnFlags = connFlags;
mCachedConnName = currentConnName;
// Only cache the connection state when we have an observer to invalidate
// it.
if (mNetworkLinkObserver) {
mConnCacheValid = true;
}
}
if (!mCacheValid) {
LOG(("ReadInternetOption: cache miss, reading from WinINet"));
nsresult rv = ReadAllOptionsLocked(mCachedConnFlags, mCachedConnName);
if (NS_FAILED(rv)) {
return rv;
}
// Only mark the cache valid when change detection is active. Without a
// KeyWatcher to invalidate it, caching would return stale data
// indefinitely.
if (mKeyWatcher) {
mCacheValid = true;
}
} else {
LOG(("ReadInternetOption: cache hit"));
}
aFlags = mCachedFlags;
switch (aOption) {
case INTERNET_PER_CONN_PROXY_SERVER:
aValue.Assign(mCachedProxyServer);
LOG(("ReadInternetOption: -> ProxyServer=[%s] flags=0x%x",
NS_ConvertUTF16toUTF8(mCachedProxyServer).get(), aFlags));
break;
case INTERNET_PER_CONN_PROXY_BYPASS:
aValue.Assign(mCachedProxyBypass);
LOG(("ReadInternetOption: -> ProxyBypass=[%s] flags=0x%x",
NS_ConvertUTF16toUTF8(mCachedProxyBypass).get(), aFlags));
break;
case INTERNET_PER_CONN_AUTOCONFIG_URL:
aValue.Assign(mCachedAutoConfigUrl);
LOG(("ReadInternetOption: -> AutoConfigUrl=[%s] flags=0x%x",
NS_ConvertUTF16toUTF8(mCachedAutoConfigUrl).get(), aFlags));
break;
default:
aValue.Truncate();
break;
}
return NS_OK;
}
} // namespace system
} // namespace toolkit
} // namespace mozilla