By moving the call to Load() up in SearchProvider::Start(), we are giving a chance...
[chromium-blink-merge.git] / net / proxy / dhcp_proxy_script_adapter_fetcher_win.cc
blobed778938c2483eebeb57a8bb819f6e8d83686759
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_proxy.h"
11 #include "base/metrics/histogram.h"
12 #include "base/sys_string_conversions.h"
13 #include "base/threading/worker_pool.h"
14 #include "base/time.h"
15 #include "net/base/net_errors.h"
16 #include "net/proxy/dhcpcsvc_init_win.h"
17 #include "net/proxy/proxy_script_fetcher_impl.h"
18 #include "net/url_request/url_request_context.h"
20 #include <windows.h>
21 #include <winsock2.h>
22 #include <dhcpcsdk.h>
23 #pragma comment(lib, "dhcpcsvc.lib")
25 namespace {
27 // Maximum amount of time to wait for response from the Win32 DHCP API.
28 const int kTimeoutMs = 2000;
30 } // namespace
32 namespace net {
34 DhcpProxyScriptAdapterFetcher::DhcpProxyScriptAdapterFetcher(
35 URLRequestContext* url_request_context)
36 : state_(STATE_START),
37 result_(ERR_IO_PENDING),
38 url_request_context_(url_request_context) {
39 DCHECK(url_request_context_);
42 DhcpProxyScriptAdapterFetcher::~DhcpProxyScriptAdapterFetcher() {
43 Cancel();
45 // The WeakPtr we passed to the worker thread may be destroyed on the
46 // worker thread. This detaches any outstanding WeakPtr state from
47 // the current thread.
48 base::SupportsWeakPtr<DhcpProxyScriptAdapterFetcher>::DetachFromThread();
51 void DhcpProxyScriptAdapterFetcher::Fetch(
52 const std::string& adapter_name, const CompletionCallback& callback) {
53 DCHECK(CalledOnValidThread());
54 DCHECK_EQ(state_, STATE_START);
55 result_ = ERR_IO_PENDING;
56 pac_script_ = string16();
57 state_ = STATE_WAIT_DHCP;
58 callback_ = callback;
60 wait_timer_.Start(FROM_HERE, ImplGetTimeout(),
61 this, &DhcpProxyScriptAdapterFetcher::OnTimeout);
62 scoped_refptr<DhcpQuery> dhcp_query(ImplCreateDhcpQuery());
63 base::WorkerPool::PostTaskAndReply(
64 FROM_HERE,
65 base::Bind(
66 &DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter,
67 dhcp_query.get(),
68 adapter_name),
69 base::Bind(
70 &DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone,
71 AsWeakPtr(),
72 dhcp_query),
73 true);
76 void DhcpProxyScriptAdapterFetcher::Cancel() {
77 DCHECK(CalledOnValidThread());
78 callback_.Reset();
79 wait_timer_.Stop();
80 script_fetcher_.reset();
82 switch (state_) {
83 case STATE_WAIT_DHCP:
84 // Nothing to do here, we let the worker thread run to completion,
85 // the task it posts back when it completes will check the state.
86 break;
87 case STATE_WAIT_URL:
88 break;
89 case STATE_START:
90 case STATE_FINISH:
91 case STATE_CANCEL:
92 break;
95 if (state_ != STATE_FINISH) {
96 result_ = ERR_ABORTED;
97 state_ = STATE_CANCEL;
101 bool DhcpProxyScriptAdapterFetcher::DidFinish() const {
102 DCHECK(CalledOnValidThread());
103 return state_ == STATE_FINISH;
106 int DhcpProxyScriptAdapterFetcher::GetResult() const {
107 DCHECK(CalledOnValidThread());
108 return result_;
111 string16 DhcpProxyScriptAdapterFetcher::GetPacScript() const {
112 DCHECK(CalledOnValidThread());
113 return pac_script_;
116 GURL DhcpProxyScriptAdapterFetcher::GetPacURL() const {
117 DCHECK(CalledOnValidThread());
118 return pac_url_;
121 DhcpProxyScriptAdapterFetcher::DhcpQuery::DhcpQuery() {
124 DhcpProxyScriptAdapterFetcher::DhcpQuery::~DhcpQuery() {
127 void DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter(
128 const std::string& adapter_name) {
129 url_ = ImplGetPacURLFromDhcp(adapter_name);
132 const std::string& DhcpProxyScriptAdapterFetcher::DhcpQuery::url() const {
133 return url_;
136 std::string
137 DhcpProxyScriptAdapterFetcher::DhcpQuery::ImplGetPacURLFromDhcp(
138 const std::string& adapter_name) {
139 return DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name);
142 void DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone(
143 scoped_refptr<DhcpQuery> dhcp_query) {
144 DCHECK(CalledOnValidThread());
145 // Because we can't cancel the call to the Win32 API, we can expect
146 // it to finish while we are in a few different states. The expected
147 // one is WAIT_DHCP, but it could be in CANCEL if Cancel() was called,
148 // or FINISH if timeout occurred.
149 DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_CANCEL ||
150 state_ == STATE_FINISH);
151 if (state_ != STATE_WAIT_DHCP)
152 return;
154 wait_timer_.Stop();
156 pac_url_ = GURL(dhcp_query->url());
157 if (pac_url_.is_empty() || !pac_url_.is_valid()) {
158 result_ = ERR_PAC_NOT_IN_DHCP;
159 TransitionToFinish();
160 } else {
161 state_ = STATE_WAIT_URL;
162 script_fetcher_.reset(ImplCreateScriptFetcher());
163 script_fetcher_->Fetch(
164 pac_url_, &pac_script_,
165 base::Bind(&DhcpProxyScriptAdapterFetcher::OnFetcherDone,
166 base::Unretained(this)));
170 void DhcpProxyScriptAdapterFetcher::OnTimeout() {
171 DCHECK_EQ(state_, STATE_WAIT_DHCP);
172 result_ = ERR_TIMED_OUT;
173 TransitionToFinish();
176 void DhcpProxyScriptAdapterFetcher::OnFetcherDone(int result) {
177 DCHECK(CalledOnValidThread());
178 DCHECK(state_ == STATE_WAIT_URL || state_ == STATE_CANCEL);
179 if (state_ == STATE_CANCEL)
180 return;
182 // At this point, pac_script_ has already been written to.
183 script_fetcher_.reset();
184 result_ = result;
185 TransitionToFinish();
188 void DhcpProxyScriptAdapterFetcher::TransitionToFinish() {
189 DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_WAIT_URL);
190 state_ = STATE_FINISH;
191 CompletionCallback callback = callback_;
192 callback_.Reset();
194 // Be careful not to touch any member state after this, as the client
195 // may delete us during this callback.
196 callback.Run(result_);
199 DhcpProxyScriptAdapterFetcher::State
200 DhcpProxyScriptAdapterFetcher::state() const {
201 return state_;
204 ProxyScriptFetcher* DhcpProxyScriptAdapterFetcher::ImplCreateScriptFetcher() {
205 return new ProxyScriptFetcherImpl(url_request_context_);
208 DhcpProxyScriptAdapterFetcher::DhcpQuery*
209 DhcpProxyScriptAdapterFetcher::ImplCreateDhcpQuery() {
210 return new DhcpQuery();
213 base::TimeDelta DhcpProxyScriptAdapterFetcher::ImplGetTimeout() const {
214 return base::TimeDelta::FromMilliseconds(kTimeoutMs);
217 // static
218 std::string DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(
219 const std::string& adapter_name) {
220 EnsureDhcpcsvcInit();
222 std::wstring adapter_name_wide = base::SysMultiByteToWide(adapter_name,
223 CP_ACP);
225 DHCPCAPI_PARAMS_ARRAY send_params = { 0, NULL };
227 BYTE option_data[] = { 1, 252 };
228 DHCPCAPI_PARAMS wpad_params = { 0 };
229 wpad_params.OptionId = 252;
230 wpad_params.IsVendor = FALSE; // Surprising, but intentional.
232 DHCPCAPI_PARAMS_ARRAY request_params = { 0 };
233 request_params.nParams = 1;
234 request_params.Params = &wpad_params;
236 // The maximum message size is typically 4096 bytes on Windows per
237 // http://support.microsoft.com/kb/321592
238 DWORD result_buffer_size = 4096;
239 scoped_ptr_malloc<BYTE> result_buffer;
240 int retry_count = 0;
241 DWORD res = NO_ERROR;
242 do {
243 result_buffer.reset(reinterpret_cast<BYTE*>(malloc(result_buffer_size)));
245 // Note that while the DHCPCAPI_REQUEST_SYNCHRONOUS flag seems to indicate
246 // there might be an asynchronous mode, there seems to be (at least in
247 // terms of well-documented use of this API) only a synchronous mode, with
248 // an optional "async notifications later if the option changes" mode.
249 // Even IE9, which we hope to emulate as IE is the most widely deployed
250 // previous implementation of the DHCP aspect of WPAD and the only one
251 // on Windows (Konqueror is the other, on Linux), uses this API with the
252 // synchronous flag. There seem to be several Microsoft Knowledge Base
253 // articles about calls to this function failing when other flags are used
254 // (e.g. http://support.microsoft.com/kb/885270) so we won't take any
255 // chances on non-standard, poorly documented usage.
256 res = ::DhcpRequestParams(DHCPCAPI_REQUEST_SYNCHRONOUS,
257 NULL,
258 const_cast<LPWSTR>(adapter_name_wide.c_str()),
259 NULL,
260 send_params, request_params,
261 result_buffer.get(), &result_buffer_size,
262 NULL);
263 ++retry_count;
264 } while (res == ERROR_MORE_DATA && retry_count <= 3);
266 if (res != NO_ERROR) {
267 LOG(INFO) << "Error fetching PAC URL from DHCP: " << res;
268 UMA_HISTOGRAM_COUNTS("Net.DhcpWpadUnhandledDhcpError", 1);
269 } else if (wpad_params.nBytesData) {
270 #ifndef NDEBUG
271 // The result should be ASCII, not wide character. Some DHCP
272 // servers appear to count the trailing NULL in nBytesData, others
273 // do not.
274 size_t count_without_null =
275 strlen(reinterpret_cast<const char*>(wpad_params.Data));
276 DCHECK(count_without_null == wpad_params.nBytesData ||
277 count_without_null + 1 == wpad_params.nBytesData);
278 #endif
279 // Belt and suspenders: First, ensure we NULL-terminate after
280 // nBytesData; this is the inner constructor with nBytesData as a
281 // parameter. Then, return only up to the first null in case of
282 // embedded NULLs; this is the outer constructor that takes the
283 // result of c_str() on the inner. If the server is giving us
284 // back a buffer with embedded NULLs, something is broken anyway.
285 return std::string(
286 std::string(reinterpret_cast<const char *>(wpad_params.Data),
287 wpad_params.nBytesData).c_str());
290 return "";
293 } // namespace net