Probably broke Win7 Tests (dbg)(6). http://build.chromium.org/p/chromium.win/builders...
[chromium-blink-merge.git] / net / proxy / dhcp_proxy_script_adapter_fetcher_win.cc
blob199014f8beccd075bf303a43f85daa24438fda42
1 // Copyright (c) 2012 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.
5 #include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h"
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop_proxy.h"
11 #include "base/metrics/histogram.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "base/task_runner.h"
15 #include "base/time/time.h"
16 #include "net/base/net_errors.h"
17 #include "net/proxy/dhcpcsvc_init_win.h"
18 #include "net/proxy/proxy_script_fetcher_impl.h"
19 #include "net/url_request/url_request_context.h"
21 #include <windows.h>
22 #include <winsock2.h>
23 #include <dhcpcsdk.h>
24 #pragma comment(lib, "dhcpcsvc.lib")
26 namespace {
28 // Maximum amount of time to wait for response from the Win32 DHCP API.
29 const int kTimeoutMs = 2000;
31 } // namespace
33 namespace net {
35 DhcpProxyScriptAdapterFetcher::DhcpProxyScriptAdapterFetcher(
36 URLRequestContext* url_request_context,
37 scoped_refptr<base::TaskRunner> task_runner)
38 : task_runner_(task_runner),
39 state_(STATE_START),
40 result_(ERR_IO_PENDING),
41 url_request_context_(url_request_context) {
42 DCHECK(url_request_context_);
45 DhcpProxyScriptAdapterFetcher::~DhcpProxyScriptAdapterFetcher() {
46 Cancel();
49 void DhcpProxyScriptAdapterFetcher::Fetch(
50 const std::string& adapter_name, const CompletionCallback& callback) {
51 DCHECK(CalledOnValidThread());
52 DCHECK_EQ(state_, STATE_START);
53 result_ = ERR_IO_PENDING;
54 pac_script_ = base::string16();
55 state_ = STATE_WAIT_DHCP;
56 callback_ = callback;
58 wait_timer_.Start(FROM_HERE, ImplGetTimeout(),
59 this, &DhcpProxyScriptAdapterFetcher::OnTimeout);
60 scoped_refptr<DhcpQuery> dhcp_query(ImplCreateDhcpQuery());
61 task_runner_->PostTaskAndReply(
62 FROM_HERE,
63 base::Bind(
64 &DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter,
65 dhcp_query.get(),
66 adapter_name),
67 base::Bind(
68 &DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone,
69 AsWeakPtr(),
70 dhcp_query));
73 void DhcpProxyScriptAdapterFetcher::Cancel() {
74 DCHECK(CalledOnValidThread());
75 callback_.Reset();
76 wait_timer_.Stop();
77 script_fetcher_.reset();
79 switch (state_) {
80 case STATE_WAIT_DHCP:
81 // Nothing to do here, we let the worker thread run to completion,
82 // the task it posts back when it completes will check the state.
83 break;
84 case STATE_WAIT_URL:
85 break;
86 case STATE_START:
87 case STATE_FINISH:
88 case STATE_CANCEL:
89 break;
92 if (state_ != STATE_FINISH) {
93 result_ = ERR_ABORTED;
94 state_ = STATE_CANCEL;
98 bool DhcpProxyScriptAdapterFetcher::DidFinish() const {
99 DCHECK(CalledOnValidThread());
100 return state_ == STATE_FINISH;
103 int DhcpProxyScriptAdapterFetcher::GetResult() const {
104 DCHECK(CalledOnValidThread());
105 return result_;
108 base::string16 DhcpProxyScriptAdapterFetcher::GetPacScript() const {
109 DCHECK(CalledOnValidThread());
110 return pac_script_;
113 GURL DhcpProxyScriptAdapterFetcher::GetPacURL() const {
114 DCHECK(CalledOnValidThread());
115 return pac_url_;
118 DhcpProxyScriptAdapterFetcher::DhcpQuery::DhcpQuery() {
121 DhcpProxyScriptAdapterFetcher::DhcpQuery::~DhcpQuery() {
124 void DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter(
125 const std::string& adapter_name) {
126 url_ = ImplGetPacURLFromDhcp(adapter_name);
129 const std::string& DhcpProxyScriptAdapterFetcher::DhcpQuery::url() const {
130 return url_;
133 std::string
134 DhcpProxyScriptAdapterFetcher::DhcpQuery::ImplGetPacURLFromDhcp(
135 const std::string& adapter_name) {
136 return DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name);
139 void DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone(
140 scoped_refptr<DhcpQuery> dhcp_query) {
141 DCHECK(CalledOnValidThread());
142 // Because we can't cancel the call to the Win32 API, we can expect
143 // it to finish while we are in a few different states. The expected
144 // one is WAIT_DHCP, but it could be in CANCEL if Cancel() was called,
145 // or FINISH if timeout occurred.
146 DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_CANCEL ||
147 state_ == STATE_FINISH);
148 if (state_ != STATE_WAIT_DHCP)
149 return;
151 wait_timer_.Stop();
153 pac_url_ = GURL(dhcp_query->url());
154 if (pac_url_.is_empty() || !pac_url_.is_valid()) {
155 result_ = ERR_PAC_NOT_IN_DHCP;
156 TransitionToFinish();
157 } else {
158 state_ = STATE_WAIT_URL;
159 script_fetcher_.reset(ImplCreateScriptFetcher());
160 script_fetcher_->Fetch(
161 pac_url_, &pac_script_,
162 base::Bind(&DhcpProxyScriptAdapterFetcher::OnFetcherDone,
163 base::Unretained(this)));
167 void DhcpProxyScriptAdapterFetcher::OnTimeout() {
168 DCHECK_EQ(state_, STATE_WAIT_DHCP);
169 result_ = ERR_TIMED_OUT;
170 TransitionToFinish();
173 void DhcpProxyScriptAdapterFetcher::OnFetcherDone(int result) {
174 DCHECK(CalledOnValidThread());
175 DCHECK(state_ == STATE_WAIT_URL || state_ == STATE_CANCEL);
176 if (state_ == STATE_CANCEL)
177 return;
179 // At this point, pac_script_ has already been written to.
180 script_fetcher_.reset();
181 result_ = result;
182 TransitionToFinish();
185 void DhcpProxyScriptAdapterFetcher::TransitionToFinish() {
186 DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_WAIT_URL);
187 state_ = STATE_FINISH;
188 CompletionCallback callback = callback_;
189 callback_.Reset();
191 // Be careful not to touch any member state after this, as the client
192 // may delete us during this callback.
193 callback.Run(result_);
196 DhcpProxyScriptAdapterFetcher::State
197 DhcpProxyScriptAdapterFetcher::state() const {
198 return state_;
201 ProxyScriptFetcher* DhcpProxyScriptAdapterFetcher::ImplCreateScriptFetcher() {
202 return new ProxyScriptFetcherImpl(url_request_context_);
205 DhcpProxyScriptAdapterFetcher::DhcpQuery*
206 DhcpProxyScriptAdapterFetcher::ImplCreateDhcpQuery() {
207 return new DhcpQuery();
210 base::TimeDelta DhcpProxyScriptAdapterFetcher::ImplGetTimeout() const {
211 return base::TimeDelta::FromMilliseconds(kTimeoutMs);
214 // static
215 std::string DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(
216 const std::string& adapter_name) {
217 EnsureDhcpcsvcInit();
219 std::wstring adapter_name_wide = base::SysMultiByteToWide(adapter_name,
220 CP_ACP);
222 DHCPCAPI_PARAMS_ARRAY send_params = { 0, NULL };
224 BYTE option_data[] = { 1, 252 };
225 DHCPCAPI_PARAMS wpad_params = { 0 };
226 wpad_params.OptionId = 252;
227 wpad_params.IsVendor = FALSE; // Surprising, but intentional.
229 DHCPCAPI_PARAMS_ARRAY request_params = { 0 };
230 request_params.nParams = 1;
231 request_params.Params = &wpad_params;
233 // The maximum message size is typically 4096 bytes on Windows per
234 // http://support.microsoft.com/kb/321592
235 DWORD result_buffer_size = 4096;
236 scoped_ptr<BYTE, base::FreeDeleter> result_buffer;
237 int retry_count = 0;
238 DWORD res = NO_ERROR;
239 do {
240 result_buffer.reset(static_cast<BYTE*>(malloc(result_buffer_size)));
242 // Note that while the DHCPCAPI_REQUEST_SYNCHRONOUS flag seems to indicate
243 // there might be an asynchronous mode, there seems to be (at least in
244 // terms of well-documented use of this API) only a synchronous mode, with
245 // an optional "async notifications later if the option changes" mode.
246 // Even IE9, which we hope to emulate as IE is the most widely deployed
247 // previous implementation of the DHCP aspect of WPAD and the only one
248 // on Windows (Konqueror is the other, on Linux), uses this API with the
249 // synchronous flag. There seem to be several Microsoft Knowledge Base
250 // articles about calls to this function failing when other flags are used
251 // (e.g. http://support.microsoft.com/kb/885270) so we won't take any
252 // chances on non-standard, poorly documented usage.
253 res = ::DhcpRequestParams(DHCPCAPI_REQUEST_SYNCHRONOUS,
254 NULL,
255 const_cast<LPWSTR>(adapter_name_wide.c_str()),
256 NULL,
257 send_params, request_params,
258 result_buffer.get(), &result_buffer_size,
259 NULL);
260 ++retry_count;
261 } while (res == ERROR_MORE_DATA && retry_count <= 3);
263 if (res != NO_ERROR) {
264 VLOG(1) << "Error fetching PAC URL from DHCP: " << res;
265 UMA_HISTOGRAM_COUNTS("Net.DhcpWpadUnhandledDhcpError", 1);
266 } else if (wpad_params.nBytesData) {
267 return SanitizeDhcpApiString(
268 reinterpret_cast<const char*>(wpad_params.Data),
269 wpad_params.nBytesData);
272 return "";
275 // static
276 std::string DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString(
277 const char* data, size_t count_bytes) {
278 // The result should be ASCII, not wide character. Some DHCP
279 // servers appear to count the trailing NULL in nBytesData, others
280 // do not. A few (we've had one report, http://crbug.com/297810)
281 // do not NULL-terminate but may \n-terminate.
283 // Belt and suspenders and elastic waistband: First, ensure we
284 // NULL-terminate after nBytesData; this is the inner constructor
285 // with nBytesData as a parameter. Then, return only up to the
286 // first null in case of embedded NULLs; this is the outer
287 // constructor that takes the result of c_str() on the inner. If
288 // the server is giving us back a buffer with embedded NULLs,
289 // something is broken anyway. Finally, trim trailing whitespace.
290 std::string result(std::string(data, count_bytes).c_str());
291 base::TrimWhitespaceASCII(result, base::TRIM_TRAILING, &result);
292 return result;
295 } // namespace net