OLD | NEW |
| (Empty) |
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/dhcp_proxy_script_adapter_fetcher_win.h" | |
6 | |
7 #include "base/message_loop_proxy.h" | |
8 #include "base/sys_string_conversions.h" | |
9 #include "base/task.h" | |
10 #include "base/threading/worker_pool.h" | |
11 #include "base/time.h" | |
12 #include "net/base/net_errors.h" | |
13 #include "net/proxy/dhcpcsvc_init_win.h" | |
14 #include "net/proxy/proxy_script_fetcher_impl.h" | |
15 #include "net/url_request/url_request_context.h" | |
16 | |
17 #include <windows.h> | |
18 #include <winsock2.h> | |
19 #include <dhcpcsdk.h> | |
20 #pragma comment(lib, "dhcpcsvc.lib") | |
21 | |
22 namespace { | |
23 | |
24 // Maximum amount of time to wait for response from the Win32 DHCP API. | |
25 const int kTimeoutMs = 2000; | |
26 | |
27 } // namespace | |
28 | |
29 namespace net { | |
30 | |
31 DhcpProxyScriptAdapterFetcher::DhcpProxyScriptAdapterFetcher( | |
32 URLRequestContext* url_request_context) | |
33 : state_(STATE_START), | |
34 result_(ERR_IO_PENDING), | |
35 callback_(NULL), | |
36 ALLOW_THIS_IN_INITIALIZER_LIST( | |
37 script_fetcher_callback_( | |
38 this, &DhcpProxyScriptAdapterFetcher::OnFetcherDone)), | |
39 url_request_context_(url_request_context) { | |
40 } | |
41 | |
42 DhcpProxyScriptAdapterFetcher::~DhcpProxyScriptAdapterFetcher() { | |
43 Cancel(); | |
44 | |
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(); | |
49 } | |
50 | |
51 void DhcpProxyScriptAdapterFetcher::Fetch( | |
52 const std::string& adapter_name, 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; | |
59 | |
60 wait_timer_.Start(ImplGetTimeout(), | |
61 this, &DhcpProxyScriptAdapterFetcher::OnTimeout); | |
62 worker_thread_ = ImplCreateWorkerThread(AsWeakPtr()); | |
63 worker_thread_->Start(adapter_name); | |
64 } | |
65 | |
66 void DhcpProxyScriptAdapterFetcher::Cancel() { | |
67 DCHECK(CalledOnValidThread()); | |
68 callback_ = NULL; | |
69 wait_timer_.Stop(); | |
70 script_fetcher_.reset(); | |
71 | |
72 switch (state_) { | |
73 case STATE_WAIT_DHCP: | |
74 // Nothing to do here, we let the worker thread run to completion, | |
75 // the task it posts back when it completes will check the state. | |
76 break; | |
77 case STATE_WAIT_URL: | |
78 break; | |
79 case STATE_START: | |
80 case STATE_FINISH: | |
81 case STATE_CANCEL: | |
82 break; | |
83 } | |
84 | |
85 if (state_ != STATE_FINISH) { | |
86 result_ = ERR_ABORTED; | |
87 state_ = STATE_CANCEL; | |
88 } | |
89 } | |
90 | |
91 bool DhcpProxyScriptAdapterFetcher::DidFinish() const { | |
92 DCHECK(CalledOnValidThread()); | |
93 return state_ == STATE_FINISH; | |
94 } | |
95 | |
96 int DhcpProxyScriptAdapterFetcher::GetResult() const { | |
97 DCHECK(CalledOnValidThread()); | |
98 return result_; | |
99 } | |
100 | |
101 string16 DhcpProxyScriptAdapterFetcher::GetPacScript() const { | |
102 DCHECK(CalledOnValidThread()); | |
103 return pac_script_; | |
104 } | |
105 | |
106 GURL DhcpProxyScriptAdapterFetcher::GetPacURL() const { | |
107 DCHECK(CalledOnValidThread()); | |
108 return pac_url_; | |
109 } | |
110 | |
111 DhcpProxyScriptAdapterFetcher::WorkerThread::WorkerThread( | |
112 const base::WeakPtr<DhcpProxyScriptAdapterFetcher>& owner) | |
113 : owner_(owner), | |
114 origin_loop_(base::MessageLoopProxy::CreateForCurrentThread()) { | |
115 } | |
116 | |
117 DhcpProxyScriptAdapterFetcher::WorkerThread::~WorkerThread() { | |
118 } | |
119 | |
120 void DhcpProxyScriptAdapterFetcher::WorkerThread::Start( | |
121 const std::string& adapter_name) { | |
122 bool succeeded = base::WorkerPool::PostTask( | |
123 FROM_HERE, | |
124 NewRunnableMethod( | |
125 this, | |
126 &DhcpProxyScriptAdapterFetcher::WorkerThread::ThreadFunc, | |
127 adapter_name), | |
128 true); | |
129 DCHECK(succeeded); | |
130 } | |
131 | |
132 void DhcpProxyScriptAdapterFetcher::WorkerThread::ThreadFunc( | |
133 const std::string& adapter_name) { | |
134 std::string url = ImplGetPacURLFromDhcp(adapter_name); | |
135 | |
136 bool succeeded = origin_loop_->PostTask( | |
137 FROM_HERE, | |
138 NewRunnableMethod( | |
139 this, | |
140 &DhcpProxyScriptAdapterFetcher::WorkerThread::OnThreadDone, | |
141 url)); | |
142 DCHECK(succeeded); | |
143 } | |
144 | |
145 void DhcpProxyScriptAdapterFetcher::WorkerThread::OnThreadDone( | |
146 const std::string& url) { | |
147 DCHECK(thread_checker_.CalledOnValidThread()); | |
148 if (owner_) | |
149 owner_->OnQueryDhcpDone(url); | |
150 } | |
151 | |
152 std::string | |
153 DhcpProxyScriptAdapterFetcher::WorkerThread::ImplGetPacURLFromDhcp( | |
154 const std::string& adapter_name) { | |
155 return DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name); | |
156 } | |
157 | |
158 void DhcpProxyScriptAdapterFetcher::OnQueryDhcpDone( | |
159 const std::string& url) { | |
160 DCHECK(CalledOnValidThread()); | |
161 // Because we can't cancel the call to the Win32 API, we can expect | |
162 // it to finish while we are in a few different states. The expected | |
163 // one is WAIT_DHCP, but it could be in CANCEL if Cancel() was called, | |
164 // or FINISH if timeout occurred. | |
165 DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_CANCEL || | |
166 state_ == STATE_FINISH); | |
167 if (state_ != STATE_WAIT_DHCP) | |
168 return; | |
169 | |
170 wait_timer_.Stop(); | |
171 | |
172 pac_url_ = GURL(url); | |
173 if (pac_url_.is_empty() || !pac_url_.is_valid()) { | |
174 result_ = ERR_PAC_NOT_IN_DHCP; | |
175 TransitionToFinish(); | |
176 } else { | |
177 state_ = STATE_WAIT_URL; | |
178 script_fetcher_.reset(ImplCreateScriptFetcher()); | |
179 script_fetcher_->Fetch(pac_url_, &pac_script_, &script_fetcher_callback_); | |
180 } | |
181 } | |
182 | |
183 void DhcpProxyScriptAdapterFetcher::OnTimeout() { | |
184 DCHECK_EQ(state_, STATE_WAIT_DHCP); | |
185 result_ = ERR_TIMED_OUT; | |
186 TransitionToFinish(); | |
187 } | |
188 | |
189 void DhcpProxyScriptAdapterFetcher::OnFetcherDone(int result) { | |
190 DCHECK(CalledOnValidThread()); | |
191 DCHECK(state_ == STATE_WAIT_URL || state_ == STATE_CANCEL); | |
192 if (state_ == STATE_CANCEL) | |
193 return; | |
194 | |
195 // At this point, pac_script_ has already been written to. | |
196 script_fetcher_.reset(); | |
197 result_ = result; | |
198 TransitionToFinish(); | |
199 } | |
200 | |
201 void DhcpProxyScriptAdapterFetcher::TransitionToFinish() { | |
202 DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_WAIT_URL); | |
203 state_ = STATE_FINISH; | |
204 callback_->Run(result_); | |
205 callback_ = NULL; | |
206 } | |
207 | |
208 ProxyScriptFetcher* DhcpProxyScriptAdapterFetcher::ImplCreateScriptFetcher() { | |
209 return new ProxyScriptFetcherImpl(url_request_context_); | |
210 } | |
211 | |
212 DhcpProxyScriptAdapterFetcher::WorkerThread* | |
213 DhcpProxyScriptAdapterFetcher::ImplCreateWorkerThread( | |
214 const base::WeakPtr<DhcpProxyScriptAdapterFetcher>& owner) { | |
215 return new WorkerThread(owner); | |
216 } | |
217 | |
218 base::TimeDelta DhcpProxyScriptAdapterFetcher::ImplGetTimeout() const { | |
219 return base::TimeDelta::FromMilliseconds(kTimeoutMs); | |
220 } | |
221 | |
222 // static | |
223 std::string DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp( | |
224 const std::string& adapter_name) { | |
225 EnsureDhcpcsvcInit(); | |
226 | |
227 std::wstring adapter_name_wide = base::SysMultiByteToWide(adapter_name, | |
228 CP_ACP); | |
229 | |
230 DHCPCAPI_PARAMS_ARRAY send_params = { 0, NULL }; | |
231 | |
232 BYTE option_data[] = { 1, 252 }; | |
233 DHCPCAPI_PARAMS wpad_params = { 0 }; | |
234 wpad_params.OptionId = 252; | |
235 wpad_params.IsVendor = FALSE; // Surprising, but intentional. | |
236 | |
237 DHCPCAPI_PARAMS_ARRAY request_params = { 0 }; | |
238 request_params.nParams = 1; | |
239 request_params.Params = &wpad_params; | |
240 | |
241 // The maximum message size is typically 4096 bytes on Windows per | |
242 // http://support.microsoft.com/kb/321592 | |
243 DWORD result_buffer_size = 4096; | |
244 scoped_ptr_malloc<BYTE> result_buffer; | |
245 int retry_count = 0; | |
246 DWORD res = NO_ERROR; | |
247 do { | |
248 result_buffer.reset(reinterpret_cast<BYTE*>(malloc(result_buffer_size))); | |
249 | |
250 // Note that while the DHCPCAPI_REQUEST_SYNCHRONOUS flag seems to indicate | |
251 // there might be an asynchronous mode, there seems to be (at least in | |
252 // terms of well-documented use of this API) only a synchronous mode, with | |
253 // an optional "async notifications later if the option changes" mode. | |
254 // Even IE9, which we hope to emulate as IE is the most widely deployed | |
255 // previous implementation of the DHCP aspect of WPAD and the only one | |
256 // on Windows (Konqueror is the other, on Linux), uses this API with the | |
257 // synchronous flag. There seem to be several Microsoft Knowledge Base | |
258 // articles about calls to this function failing when other flags are used | |
259 // (e.g. http://support.microsoft.com/kb/885270) so we won't take any | |
260 // chances on non-standard, poorly documented usage. | |
261 res = ::DhcpRequestParams(DHCPCAPI_REQUEST_SYNCHRONOUS, | |
262 NULL, | |
263 const_cast<LPWSTR>(adapter_name_wide.c_str()), | |
264 NULL, | |
265 send_params, request_params, | |
266 result_buffer.get(), &result_buffer_size, | |
267 NULL); | |
268 ++retry_count; | |
269 } while (res == ERROR_MORE_DATA && retry_count <= 3); | |
270 | |
271 if (res != NO_ERROR) { | |
272 NOTREACHED(); | |
273 } else if (wpad_params.nBytesData) { | |
274 // The result should be ASCII, not wide character. | |
275 DCHECK_EQ(strlen(reinterpret_cast<const char*>(wpad_params.Data)) + 1, | |
276 wpad_params.nBytesData); | |
277 // Return only up to the first null in case of embedded NULLs; if the | |
278 // server is giving us back a buffer with embedded NULLs, something is | |
279 // broken anyway. | |
280 return std::string(reinterpret_cast<const char *>(wpad_params.Data)); | |
281 } | |
282 | |
283 return ""; | |
284 } | |
285 | |
286 } // namespace net | |
OLD | NEW |