xref: /aosp_15_r20/external/cronet/net/proxy_resolution/win/proxy_config_service_win.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/proxy_resolution/win/proxy_config_service_win.h"
6 
7 #include <windows.h>
8 
9 #include <winhttp.h>
10 
11 #include "base/functional/bind.h"
12 #include "base/functional/callback.h"
13 #include "base/functional/callback_helpers.h"
14 #include "base/logging.h"
15 #include "base/ranges/algorithm.h"
16 #include "base/strings/string_tokenizer.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/threading/thread_restrictions.h"
20 #include "base/win/registry.h"
21 #include "base/win/scoped_handle.h"
22 #include "net/base/net_errors.h"
23 
24 namespace net {
25 
26 namespace {
27 
28 const int kPollIntervalSec = 10;
29 
FreeIEConfig(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG * ie_config)30 void FreeIEConfig(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* ie_config) {
31   if (ie_config->lpszAutoConfigUrl)
32     GlobalFree(ie_config->lpszAutoConfigUrl);
33   if (ie_config->lpszProxy)
34     GlobalFree(ie_config->lpszProxy);
35   if (ie_config->lpszProxyBypass)
36     GlobalFree(ie_config->lpszProxyBypass);
37 }
38 
39 }  // namespace
40 
ProxyConfigServiceWin(const NetworkTrafficAnnotationTag & traffic_annotation)41 ProxyConfigServiceWin::ProxyConfigServiceWin(
42     const NetworkTrafficAnnotationTag& traffic_annotation)
43     : PollingProxyConfigService(base::Seconds(kPollIntervalSec),
44                                 &ProxyConfigServiceWin::GetCurrentProxyConfig,
45                                 traffic_annotation) {
46   NetworkChangeNotifier::AddNetworkChangeObserver(this);
47 }
48 
~ProxyConfigServiceWin()49 ProxyConfigServiceWin::~ProxyConfigServiceWin() {
50   NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
51   // The registry functions below will end up going to disk.  TODO: Do this on
52   // another thread to avoid slowing the current thread.  http://crbug.com/61453
53   base::ScopedAllowBlocking scoped_allow_blocking;
54   keys_to_watch_.clear();
55 }
56 
AddObserver(Observer * observer)57 void ProxyConfigServiceWin::AddObserver(Observer* observer) {
58   // Lazily-initialize our registry watcher.
59   StartWatchingRegistryForChanges();
60 
61   // Let the super-class do its work now.
62   PollingProxyConfigService::AddObserver(observer);
63 }
64 
OnNetworkChanged(NetworkChangeNotifier::ConnectionType type)65 void ProxyConfigServiceWin::OnNetworkChanged(
66     NetworkChangeNotifier::ConnectionType type) {
67   // Proxy settings on Windows may change when the active connection changes.
68   // For instance, after connecting to a VPN, the proxy settings for the active
69   // connection will be that for the VPN. (And ProxyConfigService only reports
70   // proxy settings for the default connection).
71 
72   // This is conditioned on CONNECTION_NONE to avoid duplicating work, as
73   // NetworkChangeNotifier additionally sends it preceding completion.
74   // See https://crbug.com/1071901.
75   if (type == NetworkChangeNotifier::CONNECTION_NONE)
76     CheckForChangesNow();
77 }
78 
StartWatchingRegistryForChanges()79 void ProxyConfigServiceWin::StartWatchingRegistryForChanges() {
80   if (!keys_to_watch_.empty())
81     return;  // Already initialized.
82 
83   // The registry functions below will end up going to disk.  Do this on another
84   // thread to avoid slowing the current thread.  http://crbug.com/61453
85   base::ScopedAllowBlocking scoped_allow_blocking;
86 
87   // There are a number of different places where proxy settings can live
88   // in the registry. In some cases it appears in a binary value, in other
89   // cases string values. Furthermore winhttp and wininet appear to have
90   // separate stores, and proxy settings can be configured per-machine
91   // or per-user.
92   //
93   // This function is probably not exhaustive in the registry locations it
94   // watches for changes, however it should catch the majority of the
95   // cases. In case we have missed some less common triggers (likely), we
96   // will catch them during the periodic (10 second) polling, so things
97   // will recover.
98 
99   AddKeyToWatchList(
100       HKEY_CURRENT_USER,
101       L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings");
102 
103   AddKeyToWatchList(
104       HKEY_LOCAL_MACHINE,
105       L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings");
106 
107   AddKeyToWatchList(HKEY_LOCAL_MACHINE,
108                     L"SOFTWARE\\Policies\\Microsoft\\Windows\\CurrentVersion\\"
109                     L"Internet Settings");
110 }
111 
AddKeyToWatchList(HKEY rootkey,const wchar_t * subkey)112 bool ProxyConfigServiceWin::AddKeyToWatchList(HKEY rootkey,
113                                               const wchar_t* subkey) {
114   std::unique_ptr<base::win::RegKey> key =
115       std::make_unique<base::win::RegKey>();
116   if (key->Create(rootkey, subkey, KEY_NOTIFY) != ERROR_SUCCESS)
117     return false;
118 
119   if (!key->StartWatching(base::BindOnce(
120           &ProxyConfigServiceWin::OnObjectSignaled, base::Unretained(this),
121           base::Unretained(key.get())))) {
122     return false;
123   }
124 
125   keys_to_watch_.push_back(std::move(key));
126   return true;
127 }
128 
OnObjectSignaled(base::win::RegKey * key)129 void ProxyConfigServiceWin::OnObjectSignaled(base::win::RegKey* key) {
130   // Figure out which registry key signalled this change.
131   auto it = base::ranges::find(keys_to_watch_, key,
132                                &std::unique_ptr<base::win::RegKey>::get);
133   DCHECK(it != keys_to_watch_.end());
134 
135   // Keep watching the registry key.
136   if (!key->StartWatching(
137           base::BindOnce(&ProxyConfigServiceWin::OnObjectSignaled,
138                          base::Unretained(this), base::Unretained(key)))) {
139     keys_to_watch_.erase(it);
140   }
141 
142   // Have the PollingProxyConfigService test for changes.
143   CheckForChangesNow();
144 }
145 
146 // static
GetCurrentProxyConfig(const NetworkTrafficAnnotationTag traffic_annotation,ProxyConfigWithAnnotation * config)147 void ProxyConfigServiceWin::GetCurrentProxyConfig(
148     const NetworkTrafficAnnotationTag traffic_annotation,
149     ProxyConfigWithAnnotation* config) {
150   WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_config = {0};
151   if (!WinHttpGetIEProxyConfigForCurrentUser(&ie_config)) {
152     LOG(ERROR) << "WinHttpGetIEProxyConfigForCurrentUser failed: " <<
153         GetLastError();
154     *config = ProxyConfigWithAnnotation::CreateDirect();
155     return;
156   }
157   ProxyConfig proxy_config;
158   SetFromIEConfig(&proxy_config, ie_config);
159   FreeIEConfig(&ie_config);
160   proxy_config.set_from_system(true);
161   *config = ProxyConfigWithAnnotation(proxy_config, traffic_annotation);
162 }
163 
164 // static
SetFromIEConfig(ProxyConfig * config,const WINHTTP_CURRENT_USER_IE_PROXY_CONFIG & ie_config)165 void ProxyConfigServiceWin::SetFromIEConfig(
166     ProxyConfig* config,
167     const WINHTTP_CURRENT_USER_IE_PROXY_CONFIG& ie_config) {
168   if (ie_config.fAutoDetect)
169     config->set_auto_detect(true);
170   if (ie_config.lpszProxy) {
171     // lpszProxy may be a single proxy, or a proxy per scheme. The format
172     // is compatible with ProxyConfig::ProxyRules's string format.
173     config->proxy_rules().ParseFromString(
174         base::WideToUTF8(ie_config.lpszProxy));
175   }
176   if (ie_config.lpszProxyBypass) {
177     std::string proxy_bypass = base::WideToUTF8(ie_config.lpszProxyBypass);
178 
179     base::StringTokenizer proxy_server_bypass_list(proxy_bypass, ";, \t\n\r");
180     while (proxy_server_bypass_list.GetNext()) {
181       std::string bypass_url_domain = proxy_server_bypass_list.token();
182       config->proxy_rules().bypass_rules.AddRuleFromString(bypass_url_domain);
183     }
184   }
185   if (ie_config.lpszAutoConfigUrl)
186     config->set_pac_url(GURL(base::as_u16cstr(ie_config.lpszAutoConfigUrl)));
187 }
188 
189 }  // namespace net
190