OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h" | |
6 | |
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/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" | |
19 | |
20 #include <windows.h> | |
21 #include <winsock2.h> | |
22 #include <dhcpcsdk.h> | |
23 #pragma comment(lib, "dhcpcsvc.lib") | |
24 | |
25 namespace { | |
26 | |
27 // Maximum amount of time to wait for response from the Win32 DHCP API. | |
28 const int kTimeoutMs = 2000; | |
29 | |
30 } // namespace | |
31 | |
32 namespace net { | |
33 | |
34 DhcpProxyScriptAdapterFetcher::DhcpProxyScriptAdapterFetcher( | |
35 URLRequestContext* url_request_context, | |
36 scoped_refptr<base::TaskRunner> task_runner) | |
37 : task_runner_(task_runner), | |
38 state_(STATE_START), | |
39 result_(ERR_IO_PENDING), | |
40 url_request_context_(url_request_context) { | |
41 DCHECK(url_request_context_); | |
42 } | |
43 | |
44 DhcpProxyScriptAdapterFetcher::~DhcpProxyScriptAdapterFetcher() { | |
45 Cancel(); | |
46 } | |
47 | |
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; | |
55 callback_ = callback; | |
56 | |
57 wait_timer_.Start(FROM_HERE, ImplGetTimeout(), | |
58 this, &DhcpProxyScriptAdapterFetcher::OnTimeout); | |
59 scoped_refptr<DhcpQuery> dhcp_query(ImplCreateDhcpQuery()); | |
60 task_runner_->PostTaskAndReply( | |
61 FROM_HERE, | |
62 base::Bind( | |
63 &DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter, | |
64 dhcp_query.get(), | |
65 adapter_name), | |
66 base::Bind( | |
67 &DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone, | |
68 AsWeakPtr(), | |
69 dhcp_query)); | |
70 } | |
71 | |
72 void DhcpProxyScriptAdapterFetcher::Cancel() { | |
73 DCHECK(CalledOnValidThread()); | |
74 callback_.Reset(); | |
75 wait_timer_.Stop(); | |
76 script_fetcher_.reset(); | |
77 | |
78 switch (state_) { | |
79 case STATE_WAIT_DHCP: | |
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. | |
82 break; | |
83 case STATE_WAIT_URL: | |
84 break; | |
85 case STATE_START: | |
86 case STATE_FINISH: | |
87 case STATE_CANCEL: | |
88 break; | |
89 } | |
90 | |
91 if (state_ != STATE_FINISH) { | |
92 result_ = ERR_ABORTED; | |
93 state_ = STATE_CANCEL; | |
94 } | |
95 } | |
96 | |
97 bool DhcpProxyScriptAdapterFetcher::DidFinish() const { | |
98 DCHECK(CalledOnValidThread()); | |
99 return state_ == STATE_FINISH; | |
100 } | |
101 | |
102 int DhcpProxyScriptAdapterFetcher::GetResult() const { | |
103 DCHECK(CalledOnValidThread()); | |
104 return result_; | |
105 } | |
106 | |
107 base::string16 DhcpProxyScriptAdapterFetcher::GetPacScript() const { | |
108 DCHECK(CalledOnValidThread()); | |
109 return pac_script_; | |
110 } | |
111 | |
112 GURL DhcpProxyScriptAdapterFetcher::GetPacURL() const { | |
113 DCHECK(CalledOnValidThread()); | |
114 return pac_url_; | |
115 } | |
116 | |
117 DhcpProxyScriptAdapterFetcher::DhcpQuery::DhcpQuery() { | |
118 } | |
119 | |
120 DhcpProxyScriptAdapterFetcher::DhcpQuery::~DhcpQuery() { | |
121 } | |
122 | |
123 void DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter( | |
124 const std::string& adapter_name) { | |
125 url_ = ImplGetPacURLFromDhcp(adapter_name); | |
126 } | |
127 | |
128 const std::string& DhcpProxyScriptAdapterFetcher::DhcpQuery::url() const { | |
129 return url_; | |
130 } | |
131 | |
132 std::string | |
133 DhcpProxyScriptAdapterFetcher::DhcpQuery::ImplGetPacURLFromDhcp( | |
134 const std::string& adapter_name) { | |
135 return DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name); | |
136 } | |
137 | |
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) | |
148 return; | |
149 | |
150 wait_timer_.Stop(); | |
151 | |
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(); | |
156 } else { | |
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))); | |
163 } | |
164 } | |
165 | |
166 void DhcpProxyScriptAdapterFetcher::OnTimeout() { | |
167 DCHECK_EQ(state_, STATE_WAIT_DHCP); | |
168 result_ = ERR_TIMED_OUT; | |
169 TransitionToFinish(); | |
170 } | |
171 | |
172 void DhcpProxyScriptAdapterFetcher::OnFetcherDone(int result) { | |
173 DCHECK(CalledOnValidThread()); | |
174 DCHECK(state_ == STATE_WAIT_URL || state_ == STATE_CANCEL); | |
175 if (state_ == STATE_CANCEL) | |
176 return; | |
177 | |
178 // At this point, pac_script_ has already been written to. | |
179 script_fetcher_.reset(); | |
180 result_ = result; | |
181 TransitionToFinish(); | |
182 } | |
183 | |
184 void DhcpProxyScriptAdapterFetcher::TransitionToFinish() { | |
185 DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_WAIT_URL); | |
186 state_ = STATE_FINISH; | |
187 CompletionCallback callback = callback_; | |
188 callback_.Reset(); | |
189 | |
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_); | |
193 } | |
194 | |
195 DhcpProxyScriptAdapterFetcher::State | |
196 DhcpProxyScriptAdapterFetcher::state() const { | |
197 return state_; | |
198 } | |
199 | |
200 ProxyScriptFetcher* DhcpProxyScriptAdapterFetcher::ImplCreateScriptFetcher() { | |
201 return new ProxyScriptFetcherImpl(url_request_context_); | |
202 } | |
203 | |
204 DhcpProxyScriptAdapterFetcher::DhcpQuery* | |
205 DhcpProxyScriptAdapterFetcher::ImplCreateDhcpQuery() { | |
206 return new DhcpQuery(); | |
207 } | |
208 | |
209 base::TimeDelta DhcpProxyScriptAdapterFetcher::ImplGetTimeout() const { | |
210 return base::TimeDelta::FromMilliseconds(kTimeoutMs); | |
211 } | |
212 | |
213 // static | |
214 std::string DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp( | |
215 const std::string& adapter_name) { | |
216 EnsureDhcpcsvcInit(); | |
217 | |
218 std::wstring adapter_name_wide = base::SysMultiByteToWide(adapter_name, | |
219 CP_ACP); | |
220 | |
221 DHCPCAPI_PARAMS_ARRAY send_params = { 0, NULL }; | |
222 | |
223 DHCPCAPI_PARAMS wpad_params = { 0 }; | |
224 wpad_params.OptionId = 252; | |
225 wpad_params.IsVendor = FALSE; // Surprising, but intentional. | |
226 | |
227 DHCPCAPI_PARAMS_ARRAY request_params = { 0 }; | |
228 request_params.nParams = 1; | |
229 request_params.Params = &wpad_params; | |
230 | |
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; | |
235 int retry_count = 0; | |
236 DWORD res = NO_ERROR; | |
237 do { | |
238 result_buffer.reset(static_cast<BYTE*>(malloc(result_buffer_size))); | |
239 | |
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, | |
252 NULL, | |
253 const_cast<LPWSTR>(adapter_name_wide.c_str()), | |
254 NULL, | |
255 send_params, request_params, | |
256 result_buffer.get(), &result_buffer_size, | |
257 NULL); | |
258 ++retry_count; | |
259 } while (res == ERROR_MORE_DATA && retry_count <= 3); | |
260 | |
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); | |
267 } | |
268 | |
269 return ""; | |
270 } | |
271 | |
272 // static | |
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. | |
279 // | |
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); | |
289 return result; | |
290 } | |
291 | |
292 } // namespace net | |
OLD | NEW |