1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
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/proxy_config_service_win.h"
6
7#include <windows.h>
8#include <winhttp.h>
9
10#include "base/logging.h"
11#include "base/memory/scoped_ptr.h"
12#include "base/string_tokenizer.h"
13#include "base/string_util.h"
14#include "base/stl_util-inl.h"
15#include "base/threading/thread_restrictions.h"
16#include "base/win/registry.h"
17#include "net/base/net_errors.h"
18#include "net/proxy/proxy_config.h"
19
20#pragma comment(lib, "winhttp.lib")
21
22namespace net {
23
24namespace {
25
26const int kPollIntervalSec = 10;
27
28void FreeIEConfig(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* ie_config) {
29  if (ie_config->lpszAutoConfigUrl)
30    GlobalFree(ie_config->lpszAutoConfigUrl);
31  if (ie_config->lpszProxy)
32    GlobalFree(ie_config->lpszProxy);
33  if (ie_config->lpszProxyBypass)
34    GlobalFree(ie_config->lpszProxyBypass);
35}
36
37}  // namespace
38
39// RegKey and ObjectWatcher pair.
40class ProxyConfigServiceWin::KeyEntry {
41 public:
42  bool StartWatching(base::win::ObjectWatcher::Delegate* delegate) {
43    // Try to create a watch event for the registry key (which watches the
44    // sibling tree as well).
45    if (key_.StartWatching() != ERROR_SUCCESS)
46      return false;
47
48    // Now setup an ObjectWatcher for this event, so we get OnObjectSignaled()
49    // invoked on this message loop once it is signalled.
50    if (!watcher_.StartWatching(key_.watch_event(), delegate))
51      return false;
52
53    return true;
54  }
55
56  bool CreateRegKey(HKEY rootkey, const wchar_t* subkey) {
57    return key_.Create(rootkey, subkey, KEY_NOTIFY) == ERROR_SUCCESS;
58  }
59
60  HANDLE watch_event() const {
61    return key_.watch_event();
62  }
63
64 private:
65  base::win::RegKey key_;
66  base::win::ObjectWatcher watcher_;
67};
68
69ProxyConfigServiceWin::ProxyConfigServiceWin()
70    : PollingProxyConfigService(
71          base::TimeDelta::FromSeconds(kPollIntervalSec),
72          &ProxyConfigServiceWin::GetCurrentProxyConfig) {
73}
74
75ProxyConfigServiceWin::~ProxyConfigServiceWin() {
76  // The registry functions below will end up going to disk.  Do this on another
77  // thread to avoid slowing the IO thread.  http://crbug.com/61453
78  base::ThreadRestrictions::ScopedAllowIO allow_io;
79  STLDeleteElements(&keys_to_watch_);
80}
81
82void ProxyConfigServiceWin::AddObserver(Observer* observer) {
83  // Lazily-initialize our registry watcher.
84  StartWatchingRegistryForChanges();
85
86  // Let the super-class do its work now.
87  PollingProxyConfigService::AddObserver(observer);
88}
89
90void ProxyConfigServiceWin::StartWatchingRegistryForChanges() {
91  if (!keys_to_watch_.empty())
92    return;  // Already initialized.
93
94  // The registry functions below will end up going to disk.  Do this on another
95  // thread to avoid slowing the IO thread.  http://crbug.com/61453
96  base::ThreadRestrictions::ScopedAllowIO allow_io;
97
98  // There are a number of different places where proxy settings can live
99  // in the registry. In some cases it appears in a binary value, in other
100  // cases string values. Furthermore winhttp and wininet appear to have
101  // separate stores, and proxy settings can be configured per-machine
102  // or per-user.
103  //
104  // This function is probably not exhaustive in the registry locations it
105  // watches for changes, however it should catch the majority of the
106  // cases. In case we have missed some less common triggers (likely), we
107  // will catch them during the periodic (10 second) polling, so things
108  // will recover.
109
110  AddKeyToWatchList(
111      HKEY_CURRENT_USER,
112      L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings");
113
114  AddKeyToWatchList(
115      HKEY_LOCAL_MACHINE,
116      L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings");
117
118  AddKeyToWatchList(
119      HKEY_LOCAL_MACHINE,
120      L"SOFTWARE\\Policies\\Microsoft\\Windows\\CurrentVersion\\"
121      L"Internet Settings");
122}
123
124bool ProxyConfigServiceWin::AddKeyToWatchList(HKEY rootkey,
125                                              const wchar_t* subkey) {
126  scoped_ptr<KeyEntry> entry(new KeyEntry);
127  if (!entry->CreateRegKey(rootkey, subkey))
128    return false;
129
130  if (!entry->StartWatching(this))
131    return false;
132
133  keys_to_watch_.push_back(entry.release());
134  return true;
135}
136
137void ProxyConfigServiceWin::OnObjectSignaled(HANDLE object) {
138  // Figure out which registry key signalled this change.
139  KeyEntryList::iterator it;
140  for (it = keys_to_watch_.begin(); it != keys_to_watch_.end(); ++it) {
141    if ((*it)->watch_event() == object)
142      break;
143  }
144
145  DCHECK(it != keys_to_watch_.end());
146
147  // Keep watching the registry key.
148  if (!(*it)->StartWatching(this))
149    keys_to_watch_.erase(it);
150
151  // Have the PollingProxyConfigService test for changes.
152  CheckForChangesNow();
153}
154
155// static
156void ProxyConfigServiceWin::GetCurrentProxyConfig(ProxyConfig* config) {
157  WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_config = {0};
158  if (!WinHttpGetIEProxyConfigForCurrentUser(&ie_config)) {
159    LOG(ERROR) << "WinHttpGetIEProxyConfigForCurrentUser failed: " <<
160        GetLastError();
161    *config = ProxyConfig::CreateDirect();
162    return;
163  }
164  SetFromIEConfig(config, ie_config);
165  FreeIEConfig(&ie_config);
166}
167
168// static
169void ProxyConfigServiceWin::SetFromIEConfig(
170    ProxyConfig* config,
171    const WINHTTP_CURRENT_USER_IE_PROXY_CONFIG& ie_config) {
172  if (ie_config.fAutoDetect)
173    config->set_auto_detect(true);
174  if (ie_config.lpszProxy) {
175    // lpszProxy may be a single proxy, or a proxy per scheme. The format
176    // is compatible with ProxyConfig::ProxyRules's string format.
177    config->proxy_rules().ParseFromString(WideToASCII(ie_config.lpszProxy));
178  }
179  if (ie_config.lpszProxyBypass) {
180    std::string proxy_bypass = WideToASCII(ie_config.lpszProxyBypass);
181
182    StringTokenizer proxy_server_bypass_list(proxy_bypass, ";, \t\n\r");
183    while (proxy_server_bypass_list.GetNext()) {
184      std::string bypass_url_domain = proxy_server_bypass_list.token();
185      config->proxy_rules().bypass_rules.AddRuleFromString(bypass_url_domain);
186    }
187  }
188  if (ie_config.lpszAutoConfigUrl)
189    config->set_pac_url(GURL(ie_config.lpszAutoConfigUrl));
190}
191
192}  // namespace net
193