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