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"
8 #include "base/bind_helpers.h"
9 #include "base/location.h"
10 #include "base/logging.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/task_runner.h"
14 #include "base/time/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"
23 #pragma comment(lib, "dhcpcsvc.lib")
27 // Maximum amount of time to wait for response from the Win32 DHCP API.
28 const int kTimeoutMs
= 2000;
34 DhcpProxyScriptAdapterFetcher::DhcpProxyScriptAdapterFetcher(
35 URLRequestContext
* url_request_context
,
36 scoped_refptr
<base::TaskRunner
> task_runner
)
37 : task_runner_(task_runner
),
39 result_(ERR_IO_PENDING
),
40 url_request_context_(url_request_context
) {
41 DCHECK(url_request_context_
);
44 DhcpProxyScriptAdapterFetcher::~DhcpProxyScriptAdapterFetcher() {
48 void DhcpProxyScriptAdapterFetcher::Fetch(
49 const std::string
& adapter_name
, const CompletionCallback
& callback
) {
50 DCHECK(CalledOnValidThread());
51 DCHECK_EQ(state_
, STATE_START
);
52 result_
= ERR_IO_PENDING
;
53 pac_script_
= base::string16();
54 state_
= STATE_WAIT_DHCP
;
57 wait_timer_
.Start(FROM_HERE
, ImplGetTimeout(),
58 this, &DhcpProxyScriptAdapterFetcher::OnTimeout
);
59 scoped_refptr
<DhcpQuery
> dhcp_query(ImplCreateDhcpQuery());
60 task_runner_
->PostTaskAndReply(
63 &DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter
,
67 &DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone
,
72 void DhcpProxyScriptAdapterFetcher::Cancel() {
73 DCHECK(CalledOnValidThread());
76 script_fetcher_
.reset();
80 // Nothing to do here, we let the worker thread run to completion,
81 // the task it posts back when it completes will check the state.
91 if (state_
!= STATE_FINISH
) {
92 result_
= ERR_ABORTED
;
93 state_
= STATE_CANCEL
;
97 bool DhcpProxyScriptAdapterFetcher::DidFinish() const {
98 DCHECK(CalledOnValidThread());
99 return state_
== STATE_FINISH
;
102 int DhcpProxyScriptAdapterFetcher::GetResult() const {
103 DCHECK(CalledOnValidThread());
107 base::string16
DhcpProxyScriptAdapterFetcher::GetPacScript() const {
108 DCHECK(CalledOnValidThread());
112 GURL
DhcpProxyScriptAdapterFetcher::GetPacURL() const {
113 DCHECK(CalledOnValidThread());
117 DhcpProxyScriptAdapterFetcher::DhcpQuery::DhcpQuery() {
120 void DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter(
121 const std::string
& adapter_name
) {
122 url_
= ImplGetPacURLFromDhcp(adapter_name
);
125 const std::string
& DhcpProxyScriptAdapterFetcher::DhcpQuery::url() const {
130 DhcpProxyScriptAdapterFetcher::DhcpQuery::ImplGetPacURLFromDhcp(
131 const std::string
& adapter_name
) {
132 return DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name
);
135 DhcpProxyScriptAdapterFetcher::DhcpQuery::~DhcpQuery() {
138 void DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone(
139 scoped_refptr
<DhcpQuery
> dhcp_query
) {
140 DCHECK(CalledOnValidThread());
141 // Because we can't cancel the call to the Win32 API, we can expect
142 // it to finish while we are in a few different states. The expected
143 // one is WAIT_DHCP, but it could be in CANCEL if Cancel() was called,
144 // or FINISH if timeout occurred.
145 DCHECK(state_
== STATE_WAIT_DHCP
|| state_
== STATE_CANCEL
||
146 state_
== STATE_FINISH
);
147 if (state_
!= STATE_WAIT_DHCP
)
152 pac_url_
= GURL(dhcp_query
->url());
153 if (pac_url_
.is_empty() || !pac_url_
.is_valid()) {
154 result_
= ERR_PAC_NOT_IN_DHCP
;
155 TransitionToFinish();
157 state_
= STATE_WAIT_URL
;
158 script_fetcher_
.reset(ImplCreateScriptFetcher());
159 script_fetcher_
->Fetch(
160 pac_url_
, &pac_script_
,
161 base::Bind(&DhcpProxyScriptAdapterFetcher::OnFetcherDone
,
162 base::Unretained(this)));
166 void DhcpProxyScriptAdapterFetcher::OnTimeout() {
167 DCHECK_EQ(state_
, STATE_WAIT_DHCP
);
168 result_
= ERR_TIMED_OUT
;
169 TransitionToFinish();
172 void DhcpProxyScriptAdapterFetcher::OnFetcherDone(int result
) {
173 DCHECK(CalledOnValidThread());
174 DCHECK(state_
== STATE_WAIT_URL
|| state_
== STATE_CANCEL
);
175 if (state_
== STATE_CANCEL
)
178 // At this point, pac_script_ has already been written to.
179 script_fetcher_
.reset();
181 TransitionToFinish();
184 void DhcpProxyScriptAdapterFetcher::TransitionToFinish() {
185 DCHECK(state_
== STATE_WAIT_DHCP
|| state_
== STATE_WAIT_URL
);
186 state_
= STATE_FINISH
;
187 CompletionCallback callback
= callback_
;
190 // Be careful not to touch any member state after this, as the client
191 // may delete us during this callback.
192 callback
.Run(result_
);
195 DhcpProxyScriptAdapterFetcher::State
196 DhcpProxyScriptAdapterFetcher::state() const {
200 ProxyScriptFetcher
* DhcpProxyScriptAdapterFetcher::ImplCreateScriptFetcher() {
201 return new ProxyScriptFetcherImpl(url_request_context_
);
204 DhcpProxyScriptAdapterFetcher::DhcpQuery
*
205 DhcpProxyScriptAdapterFetcher::ImplCreateDhcpQuery() {
206 return new DhcpQuery();
209 base::TimeDelta
DhcpProxyScriptAdapterFetcher::ImplGetTimeout() const {
210 return base::TimeDelta::FromMilliseconds(kTimeoutMs
);
214 std::string
DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(
215 const std::string
& adapter_name
) {
216 EnsureDhcpcsvcInit();
218 std::wstring adapter_name_wide
= base::SysMultiByteToWide(adapter_name
,
221 DHCPCAPI_PARAMS_ARRAY send_params
= { 0, NULL
};
223 DHCPCAPI_PARAMS wpad_params
= { 0 };
224 wpad_params
.OptionId
= 252;
225 wpad_params
.IsVendor
= FALSE
; // Surprising, but intentional.
227 DHCPCAPI_PARAMS_ARRAY request_params
= { 0 };
228 request_params
.nParams
= 1;
229 request_params
.Params
= &wpad_params
;
231 // The maximum message size is typically 4096 bytes on Windows per
232 // http://support.microsoft.com/kb/321592
233 DWORD result_buffer_size
= 4096;
234 scoped_ptr
<BYTE
, base::FreeDeleter
> result_buffer
;
236 DWORD res
= NO_ERROR
;
238 result_buffer
.reset(static_cast<BYTE
*>(malloc(result_buffer_size
)));
240 // Note that while the DHCPCAPI_REQUEST_SYNCHRONOUS flag seems to indicate
241 // there might be an asynchronous mode, there seems to be (at least in
242 // terms of well-documented use of this API) only a synchronous mode, with
243 // an optional "async notifications later if the option changes" mode.
244 // Even IE9, which we hope to emulate as IE is the most widely deployed
245 // previous implementation of the DHCP aspect of WPAD and the only one
246 // on Windows (Konqueror is the other, on Linux), uses this API with the
247 // synchronous flag. There seem to be several Microsoft Knowledge Base
248 // articles about calls to this function failing when other flags are used
249 // (e.g. http://support.microsoft.com/kb/885270) so we won't take any
250 // chances on non-standard, poorly documented usage.
251 res
= ::DhcpRequestParams(DHCPCAPI_REQUEST_SYNCHRONOUS
,
253 const_cast<LPWSTR
>(adapter_name_wide
.c_str()),
255 send_params
, request_params
,
256 result_buffer
.get(), &result_buffer_size
,
259 } while (res
== ERROR_MORE_DATA
&& retry_count
<= 3);
261 if (res
!= NO_ERROR
) {
262 VLOG(1) << "Error fetching PAC URL from DHCP: " << res
;
263 } else if (wpad_params
.nBytesData
) {
264 return SanitizeDhcpApiString(
265 reinterpret_cast<const char*>(wpad_params
.Data
),
266 wpad_params
.nBytesData
);
273 std::string
DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString(
274 const char* data
, size_t count_bytes
) {
275 // The result should be ASCII, not wide character. Some DHCP
276 // servers appear to count the trailing NULL in nBytesData, others
277 // do not. A few (we've had one report, http://crbug.com/297810)
278 // do not NULL-terminate but may \n-terminate.
280 // Belt and suspenders and elastic waistband: First, ensure we
281 // NULL-terminate after nBytesData; this is the inner constructor
282 // with nBytesData as a parameter. Then, return only up to the
283 // first null in case of embedded NULLs; this is the outer
284 // constructor that takes the result of c_str() on the inner. If
285 // the server is giving us back a buffer with embedded NULLs,
286 // something is broken anyway. Finally, trim trailing whitespace.
287 std::string
result(std::string(data
, count_bytes
).c_str());
288 base::TrimWhitespaceASCII(result
, base::TRIM_TRAILING
, &result
);