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