• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 The Chromium Authors
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_resolution/win/dhcp_pac_file_adapter_fetcher_win.h"
6 
7 #include "base/functional/bind.h"
8 #include "base/functional/callback_helpers.h"
9 #include "base/location.h"
10 #include "base/logging.h"
11 #include "base/memory/free_deleter.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "base/task/task_runner.h"
15 #include "base/threading/scoped_blocking_call.h"
16 #include "base/time/time.h"
17 #include "net/proxy_resolution/pac_file_fetcher_impl.h"
18 #include "net/proxy_resolution/win/dhcpcsvc_init_win.h"
19 #include "net/url_request/url_request_context.h"
20 
21 #include <windows.h>
22 #include <winsock2.h>
23 #include <dhcpcsdk.h>
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 
DhcpPacFileAdapterFetcher(URLRequestContext * url_request_context,scoped_refptr<base::TaskRunner> task_runner)34 DhcpPacFileAdapterFetcher::DhcpPacFileAdapterFetcher(
35     URLRequestContext* url_request_context,
36     scoped_refptr<base::TaskRunner> task_runner)
37     : task_runner_(task_runner), url_request_context_(url_request_context) {
38   DCHECK(url_request_context_);
39 }
40 
~DhcpPacFileAdapterFetcher()41 DhcpPacFileAdapterFetcher::~DhcpPacFileAdapterFetcher() {
42   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
43   Cancel();
44 }
45 
Fetch(const std::string & adapter_name,CompletionOnceCallback callback,const NetworkTrafficAnnotationTag traffic_annotation)46 void DhcpPacFileAdapterFetcher::Fetch(
47     const std::string& adapter_name,
48     CompletionOnceCallback callback,
49     const NetworkTrafficAnnotationTag traffic_annotation) {
50   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
51   DCHECK_EQ(state_, STATE_START);
52   result_ = ERR_IO_PENDING;
53   pac_script_ = std::u16string();
54   state_ = STATE_WAIT_DHCP;
55   callback_ = std::move(callback);
56 
57   wait_timer_.Start(FROM_HERE, ImplGetTimeout(), this,
58                     &DhcpPacFileAdapterFetcher::OnTimeout);
59   scoped_refptr<DhcpQuery> dhcp_query(ImplCreateDhcpQuery());
60   task_runner_->PostTaskAndReply(
61       FROM_HERE,
62       base::BindOnce(&DhcpPacFileAdapterFetcher::DhcpQuery::GetPacURLForAdapter,
63                      dhcp_query.get(), adapter_name),
64       base::BindOnce(&DhcpPacFileAdapterFetcher::OnDhcpQueryDone, AsWeakPtr(),
65                      dhcp_query, traffic_annotation));
66 }
67 
Cancel()68 void DhcpPacFileAdapterFetcher::Cancel() {
69   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
70   callback_.Reset();
71   wait_timer_.Stop();
72   script_fetcher_.reset();
73 
74   switch (state_) {
75     case STATE_WAIT_DHCP:
76       // Nothing to do here, we let the worker thread run to completion,
77       // the task it posts back when it completes will check the state.
78       break;
79     case STATE_WAIT_URL:
80       break;
81     case STATE_START:
82     case STATE_FINISH:
83     case STATE_CANCEL:
84       break;
85   }
86 
87   if (state_ != STATE_FINISH) {
88     result_ = ERR_ABORTED;
89     state_ = STATE_CANCEL;
90   }
91 }
92 
DidFinish() const93 bool DhcpPacFileAdapterFetcher::DidFinish() const {
94   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
95   return state_ == STATE_FINISH;
96 }
97 
GetResult() const98 int DhcpPacFileAdapterFetcher::GetResult() const {
99   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
100   return result_;
101 }
102 
GetPacScript() const103 std::u16string DhcpPacFileAdapterFetcher::GetPacScript() const {
104   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
105   return pac_script_;
106 }
107 
GetPacURL() const108 GURL DhcpPacFileAdapterFetcher::GetPacURL() const {
109   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
110   return pac_url_;
111 }
112 
113 DhcpPacFileAdapterFetcher::DhcpQuery::DhcpQuery() = default;
114 
GetPacURLForAdapter(const std::string & adapter_name)115 void DhcpPacFileAdapterFetcher::DhcpQuery::GetPacURLForAdapter(
116     const std::string& adapter_name) {
117   url_ = ImplGetPacURLFromDhcp(adapter_name);
118 }
119 
url() const120 const std::string& DhcpPacFileAdapterFetcher::DhcpQuery::url() const {
121   return url_;
122 }
123 
ImplGetPacURLFromDhcp(const std::string & adapter_name)124 std::string DhcpPacFileAdapterFetcher::DhcpQuery::ImplGetPacURLFromDhcp(
125     const std::string& adapter_name) {
126   return DhcpPacFileAdapterFetcher::GetPacURLFromDhcp(adapter_name);
127 }
128 
129 DhcpPacFileAdapterFetcher::DhcpQuery::~DhcpQuery() = default;
130 
OnDhcpQueryDone(scoped_refptr<DhcpQuery> dhcp_query,const NetworkTrafficAnnotationTag traffic_annotation)131 void DhcpPacFileAdapterFetcher::OnDhcpQueryDone(
132     scoped_refptr<DhcpQuery> dhcp_query,
133     const NetworkTrafficAnnotationTag traffic_annotation) {
134   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
135   // Because we can't cancel the call to the Win32 API, we can expect
136   // it to finish while we are in a few different states.  The expected
137   // one is WAIT_DHCP, but it could be in CANCEL if Cancel() was called,
138   // or FINISH if timeout occurred.
139   DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_CANCEL ||
140          state_ == STATE_FINISH);
141   if (state_ != STATE_WAIT_DHCP)
142     return;
143 
144   wait_timer_.Stop();
145 
146   pac_url_ = GURL(dhcp_query->url());
147   if (pac_url_.is_empty() || !pac_url_.is_valid()) {
148     result_ = ERR_PAC_NOT_IN_DHCP;
149     TransitionToFinish();
150   } else {
151     state_ = STATE_WAIT_URL;
152     script_fetcher_ = ImplCreateScriptFetcher();
153     script_fetcher_->Fetch(
154         pac_url_, &pac_script_,
155         base::BindOnce(&DhcpPacFileAdapterFetcher::OnFetcherDone,
156                        base::Unretained(this)),
157         traffic_annotation);
158   }
159 }
160 
OnTimeout()161 void DhcpPacFileAdapterFetcher::OnTimeout() {
162   DCHECK_EQ(state_, STATE_WAIT_DHCP);
163   result_ = ERR_TIMED_OUT;
164   TransitionToFinish();
165 }
166 
OnFetcherDone(int result)167 void DhcpPacFileAdapterFetcher::OnFetcherDone(int result) {
168   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
169   DCHECK(state_ == STATE_WAIT_URL || state_ == STATE_CANCEL);
170   if (state_ == STATE_CANCEL)
171     return;
172 
173   // At this point, pac_script_ has already been written to.
174   script_fetcher_.reset();
175   result_ = result;
176   TransitionToFinish();
177 }
178 
TransitionToFinish()179 void DhcpPacFileAdapterFetcher::TransitionToFinish() {
180   DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_WAIT_URL);
181   state_ = STATE_FINISH;
182 
183   // Be careful not to touch any member state after this, as the client
184   // may delete us during this callback.
185   std::move(callback_).Run(result_);
186 }
187 
state() const188 DhcpPacFileAdapterFetcher::State DhcpPacFileAdapterFetcher::state() const {
189   return state_;
190 }
191 
192 std::unique_ptr<PacFileFetcher>
ImplCreateScriptFetcher()193 DhcpPacFileAdapterFetcher::ImplCreateScriptFetcher() {
194   return PacFileFetcherImpl::Create(url_request_context_);
195 }
196 
197 scoped_refptr<DhcpPacFileAdapterFetcher::DhcpQuery>
ImplCreateDhcpQuery()198 DhcpPacFileAdapterFetcher::ImplCreateDhcpQuery() {
199   return base::MakeRefCounted<DhcpQuery>();
200 }
201 
ImplGetTimeout() const202 base::TimeDelta DhcpPacFileAdapterFetcher::ImplGetTimeout() const {
203   return base::Milliseconds(kTimeoutMs);
204 }
205 
206 // static
GetPacURLFromDhcp(const std::string & adapter_name)207 std::string DhcpPacFileAdapterFetcher::GetPacURLFromDhcp(
208     const std::string& adapter_name) {
209   EnsureDhcpcsvcInit();
210 
211   std::wstring adapter_name_wide = base::SysMultiByteToWide(adapter_name,
212                                                             CP_ACP);
213 
214   DHCPCAPI_PARAMS_ARRAY send_params = {0, nullptr};
215 
216   DHCPCAPI_PARAMS wpad_params = { 0 };
217   wpad_params.OptionId = 252;
218   wpad_params.IsVendor = FALSE;  // Surprising, but intentional.
219 
220   DHCPCAPI_PARAMS_ARRAY request_params = { 0 };
221   request_params.nParams = 1;
222   request_params.Params = &wpad_params;
223 
224   // The maximum message size is typically 4096 bytes on Windows per
225   // http://support.microsoft.com/kb/321592
226   DWORD result_buffer_size = 4096;
227   std::unique_ptr<BYTE, base::FreeDeleter> result_buffer;
228   int retry_count = 0;
229   DWORD res = NO_ERROR;
230   do {
231     result_buffer.reset(static_cast<BYTE*>(malloc(result_buffer_size)));
232 
233     // Note that while the DHCPCAPI_REQUEST_SYNCHRONOUS flag seems to indicate
234     // there might be an asynchronous mode, there seems to be (at least in
235     // terms of well-documented use of this API) only a synchronous mode, with
236     // an optional "async notifications later if the option changes" mode.
237     // Even IE9, which we hope to emulate as IE is the most widely deployed
238     // previous implementation of the DHCP aspect of WPAD and the only one
239     // on Windows (Konqueror is the other, on Linux), uses this API with the
240     // synchronous flag.  There seem to be several Microsoft Knowledge Base
241     // articles about calls to this function failing when other flags are used
242     // (e.g. http://support.microsoft.com/kb/885270) so we won't take any
243     // chances on non-standard, poorly documented usage.
244     base::ScopedBlockingCall scoped_blocking_call(
245         FROM_HERE, base::BlockingType::MAY_BLOCK);
246     res = ::DhcpRequestParams(
247         DHCPCAPI_REQUEST_SYNCHRONOUS, nullptr,
248         const_cast<LPWSTR>(adapter_name_wide.c_str()), nullptr, send_params,
249         request_params, result_buffer.get(), &result_buffer_size, nullptr);
250     ++retry_count;
251   } while (res == ERROR_MORE_DATA && retry_count <= 3);
252 
253   if (res != NO_ERROR) {
254     VLOG(1) << "Error fetching PAC URL from DHCP: " << res;
255   } else if (wpad_params.nBytesData) {
256     return SanitizeDhcpApiString(
257         reinterpret_cast<const char*>(wpad_params.Data),
258         wpad_params.nBytesData);
259   }
260 
261   return "";
262 }
263 
264 // static
SanitizeDhcpApiString(const char * data,size_t count_bytes)265 std::string DhcpPacFileAdapterFetcher::SanitizeDhcpApiString(
266     const char* data,
267     size_t count_bytes) {
268   // The result should be ASCII, not wide character.  Some DHCP
269   // servers appear to count the trailing NULL in nBytesData, others
270   // do not.  A few (we've had one report, http://crbug.com/297810)
271   // do not NULL-terminate but may \n-terminate.
272   //
273   // Belt and suspenders and elastic waistband: First, ensure we
274   // NULL-terminate after nBytesData; this is the inner constructor
275   // with nBytesData as a parameter.  Then, return only up to the
276   // first null in case of embedded NULLs; this is the outer
277   // constructor that takes the result of c_str() on the inner.  If
278   // the server is giving us back a buffer with embedded NULLs,
279   // something is broken anyway.  Finally, trim trailing whitespace.
280   std::string result(std::string(data, count_bytes).c_str());
281   base::TrimWhitespaceASCII(result, base::TRIM_TRAILING, &result);
282   return result;
283 }
284 
285 }  // namespace net
286