| // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "net/proxy_resolution/dhcp_pac_file_adapter_fetcher_win.h" | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/bind_helpers.h" | 
 | #include "base/location.h" | 
 | #include "base/logging.h" | 
 | #include "base/memory/free_deleter.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/strings/sys_string_conversions.h" | 
 | #include "base/task_runner.h" | 
 | #include "base/threading/scoped_blocking_call.h" | 
 | #include "base/time/time.h" | 
 | #include "net/base/net_errors.h" | 
 | #include "net/proxy_resolution/dhcpcsvc_init_win.h" | 
 | #include "net/proxy_resolution/pac_file_fetcher_impl.h" | 
 | #include "net/url_request/url_request_context.h" | 
 |  | 
 | #include <windows.h> | 
 | #include <winsock2.h> | 
 | #include <dhcpcsdk.h> | 
 |  | 
 | namespace { | 
 |  | 
 | // Maximum amount of time to wait for response from the Win32 DHCP API. | 
 | const int kTimeoutMs = 2000; | 
 |  | 
 | }  // namespace | 
 |  | 
 | namespace net { | 
 |  | 
 | DhcpPacFileAdapterFetcher::DhcpPacFileAdapterFetcher( | 
 |     URLRequestContext* url_request_context, | 
 |     scoped_refptr<base::TaskRunner> task_runner) | 
 |     : task_runner_(task_runner), | 
 |       state_(STATE_START), | 
 |       result_(ERR_IO_PENDING), | 
 |       url_request_context_(url_request_context) { | 
 |   DCHECK(url_request_context_); | 
 | } | 
 |  | 
 | DhcpPacFileAdapterFetcher::~DhcpPacFileAdapterFetcher() { | 
 |   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
 |   Cancel(); | 
 | } | 
 |  | 
 | void DhcpPacFileAdapterFetcher::Fetch( | 
 |     const std::string& adapter_name, | 
 |     CompletionOnceCallback callback, | 
 |     const NetworkTrafficAnnotationTag traffic_annotation) { | 
 |   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
 |   DCHECK_EQ(state_, STATE_START); | 
 |   result_ = ERR_IO_PENDING; | 
 |   pac_script_ = base::string16(); | 
 |   state_ = STATE_WAIT_DHCP; | 
 |   callback_ = std::move(callback); | 
 |  | 
 |   wait_timer_.Start(FROM_HERE, ImplGetTimeout(), this, | 
 |                     &DhcpPacFileAdapterFetcher::OnTimeout); | 
 |   scoped_refptr<DhcpQuery> dhcp_query(ImplCreateDhcpQuery()); | 
 |   task_runner_->PostTaskAndReply( | 
 |       FROM_HERE, | 
 |       base::Bind(&DhcpPacFileAdapterFetcher::DhcpQuery::GetPacURLForAdapter, | 
 |                  dhcp_query.get(), adapter_name), | 
 |       base::Bind(&DhcpPacFileAdapterFetcher::OnDhcpQueryDone, AsWeakPtr(), | 
 |                  dhcp_query, traffic_annotation)); | 
 | } | 
 |  | 
 | void DhcpPacFileAdapterFetcher::Cancel() { | 
 |   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
 |   callback_.Reset(); | 
 |   wait_timer_.Stop(); | 
 |   script_fetcher_.reset(); | 
 |  | 
 |   switch (state_) { | 
 |     case STATE_WAIT_DHCP: | 
 |       // Nothing to do here, we let the worker thread run to completion, | 
 |       // the task it posts back when it completes will check the state. | 
 |       break; | 
 |     case STATE_WAIT_URL: | 
 |       break; | 
 |     case STATE_START: | 
 |     case STATE_FINISH: | 
 |     case STATE_CANCEL: | 
 |       break; | 
 |   } | 
 |  | 
 |   if (state_ != STATE_FINISH) { | 
 |     result_ = ERR_ABORTED; | 
 |     state_ = STATE_CANCEL; | 
 |   } | 
 | } | 
 |  | 
 | bool DhcpPacFileAdapterFetcher::DidFinish() const { | 
 |   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
 |   return state_ == STATE_FINISH; | 
 | } | 
 |  | 
 | int DhcpPacFileAdapterFetcher::GetResult() const { | 
 |   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
 |   return result_; | 
 | } | 
 |  | 
 | base::string16 DhcpPacFileAdapterFetcher::GetPacScript() const { | 
 |   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
 |   return pac_script_; | 
 | } | 
 |  | 
 | GURL DhcpPacFileAdapterFetcher::GetPacURL() const { | 
 |   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
 |   return pac_url_; | 
 | } | 
 |  | 
 | DhcpPacFileAdapterFetcher::DhcpQuery::DhcpQuery() {} | 
 |  | 
 | void DhcpPacFileAdapterFetcher::DhcpQuery::GetPacURLForAdapter( | 
 |     const std::string& adapter_name) { | 
 |   url_ = ImplGetPacURLFromDhcp(adapter_name); | 
 | } | 
 |  | 
 | const std::string& DhcpPacFileAdapterFetcher::DhcpQuery::url() const { | 
 |   return url_; | 
 | } | 
 |  | 
 | std::string DhcpPacFileAdapterFetcher::DhcpQuery::ImplGetPacURLFromDhcp( | 
 |     const std::string& adapter_name) { | 
 |   return DhcpPacFileAdapterFetcher::GetPacURLFromDhcp(adapter_name); | 
 | } | 
 |  | 
 | DhcpPacFileAdapterFetcher::DhcpQuery::~DhcpQuery() {} | 
 |  | 
 | void DhcpPacFileAdapterFetcher::OnDhcpQueryDone( | 
 |     scoped_refptr<DhcpQuery> dhcp_query, | 
 |     const NetworkTrafficAnnotationTag traffic_annotation) { | 
 |   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
 |   // Because we can't cancel the call to the Win32 API, we can expect | 
 |   // it to finish while we are in a few different states.  The expected | 
 |   // one is WAIT_DHCP, but it could be in CANCEL if Cancel() was called, | 
 |   // or FINISH if timeout occurred. | 
 |   DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_CANCEL || | 
 |          state_ == STATE_FINISH); | 
 |   if (state_ != STATE_WAIT_DHCP) | 
 |     return; | 
 |  | 
 |   wait_timer_.Stop(); | 
 |  | 
 |   pac_url_ = GURL(dhcp_query->url()); | 
 |   if (pac_url_.is_empty() || !pac_url_.is_valid()) { | 
 |     result_ = ERR_PAC_NOT_IN_DHCP; | 
 |     TransitionToFinish(); | 
 |   } else { | 
 |     state_ = STATE_WAIT_URL; | 
 |     script_fetcher_ = ImplCreateScriptFetcher(); | 
 |     script_fetcher_->Fetch(pac_url_, &pac_script_, | 
 |                            base::Bind(&DhcpPacFileAdapterFetcher::OnFetcherDone, | 
 |                                       base::Unretained(this)), | 
 |                            traffic_annotation); | 
 |   } | 
 | } | 
 |  | 
 | void DhcpPacFileAdapterFetcher::OnTimeout() { | 
 |   DCHECK_EQ(state_, STATE_WAIT_DHCP); | 
 |   result_ = ERR_TIMED_OUT; | 
 |   TransitionToFinish(); | 
 | } | 
 |  | 
 | void DhcpPacFileAdapterFetcher::OnFetcherDone(int result) { | 
 |   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); | 
 |   DCHECK(state_ == STATE_WAIT_URL || state_ == STATE_CANCEL); | 
 |   if (state_ == STATE_CANCEL) | 
 |     return; | 
 |  | 
 |   // At this point, pac_script_ has already been written to. | 
 |   script_fetcher_.reset(); | 
 |   result_ = result; | 
 |   TransitionToFinish(); | 
 | } | 
 |  | 
 | void DhcpPacFileAdapterFetcher::TransitionToFinish() { | 
 |   DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_WAIT_URL); | 
 |   state_ = STATE_FINISH; | 
 |  | 
 |   // Be careful not to touch any member state after this, as the client | 
 |   // may delete us during this callback. | 
 |   std::move(callback_).Run(result_); | 
 | } | 
 |  | 
 | DhcpPacFileAdapterFetcher::State DhcpPacFileAdapterFetcher::state() const { | 
 |   return state_; | 
 | } | 
 |  | 
 | std::unique_ptr<PacFileFetcher> | 
 | DhcpPacFileAdapterFetcher::ImplCreateScriptFetcher() { | 
 |   return PacFileFetcherImpl::Create(url_request_context_); | 
 | } | 
 |  | 
 | DhcpPacFileAdapterFetcher::DhcpQuery* | 
 | DhcpPacFileAdapterFetcher::ImplCreateDhcpQuery() { | 
 |   return new DhcpQuery(); | 
 | } | 
 |  | 
 | base::TimeDelta DhcpPacFileAdapterFetcher::ImplGetTimeout() const { | 
 |   return base::TimeDelta::FromMilliseconds(kTimeoutMs); | 
 | } | 
 |  | 
 | // static | 
 | std::string DhcpPacFileAdapterFetcher::GetPacURLFromDhcp( | 
 |     const std::string& adapter_name) { | 
 |   EnsureDhcpcsvcInit(); | 
 |  | 
 |   std::wstring adapter_name_wide = base::SysMultiByteToWide(adapter_name, | 
 |                                                             CP_ACP); | 
 |  | 
 |   DHCPCAPI_PARAMS_ARRAY send_params = {0, nullptr}; | 
 |  | 
 |   DHCPCAPI_PARAMS wpad_params = { 0 }; | 
 |   wpad_params.OptionId = 252; | 
 |   wpad_params.IsVendor = FALSE;  // Surprising, but intentional. | 
 |  | 
 |   DHCPCAPI_PARAMS_ARRAY request_params = { 0 }; | 
 |   request_params.nParams = 1; | 
 |   request_params.Params = &wpad_params; | 
 |  | 
 |   // The maximum message size is typically 4096 bytes on Windows per | 
 |   // http://support.microsoft.com/kb/321592 | 
 |   DWORD result_buffer_size = 4096; | 
 |   std::unique_ptr<BYTE, base::FreeDeleter> result_buffer; | 
 |   int retry_count = 0; | 
 |   DWORD res = NO_ERROR; | 
 |   do { | 
 |     result_buffer.reset(static_cast<BYTE*>(malloc(result_buffer_size))); | 
 |  | 
 |     // Note that while the DHCPCAPI_REQUEST_SYNCHRONOUS flag seems to indicate | 
 |     // there might be an asynchronous mode, there seems to be (at least in | 
 |     // terms of well-documented use of this API) only a synchronous mode, with | 
 |     // an optional "async notifications later if the option changes" mode. | 
 |     // Even IE9, which we hope to emulate as IE is the most widely deployed | 
 |     // previous implementation of the DHCP aspect of WPAD and the only one | 
 |     // on Windows (Konqueror is the other, on Linux), uses this API with the | 
 |     // synchronous flag.  There seem to be several Microsoft Knowledge Base | 
 |     // articles about calls to this function failing when other flags are used | 
 |     // (e.g. http://support.microsoft.com/kb/885270) so we won't take any | 
 |     // chances on non-standard, poorly documented usage. | 
 |     base::ScopedBlockingCall scoped_blocking_call( | 
 |         FROM_HERE, base::BlockingType::MAY_BLOCK); | 
 |     res = ::DhcpRequestParams( | 
 |         DHCPCAPI_REQUEST_SYNCHRONOUS, nullptr, | 
 |         const_cast<LPWSTR>(adapter_name_wide.c_str()), nullptr, send_params, | 
 |         request_params, result_buffer.get(), &result_buffer_size, nullptr); | 
 |     ++retry_count; | 
 |   } while (res == ERROR_MORE_DATA && retry_count <= 3); | 
 |  | 
 |   if (res != NO_ERROR) { | 
 |     VLOG(1) << "Error fetching PAC URL from DHCP: " << res; | 
 |   } else if (wpad_params.nBytesData) { | 
 |     return SanitizeDhcpApiString( | 
 |         reinterpret_cast<const char*>(wpad_params.Data), | 
 |         wpad_params.nBytesData); | 
 |   } | 
 |  | 
 |   return ""; | 
 | } | 
 |  | 
 | // static | 
 | std::string DhcpPacFileAdapterFetcher::SanitizeDhcpApiString( | 
 |     const char* data, | 
 |     size_t count_bytes) { | 
 |   // The result should be ASCII, not wide character.  Some DHCP | 
 |   // servers appear to count the trailing NULL in nBytesData, others | 
 |   // do not.  A few (we've had one report, http://crbug.com/297810) | 
 |   // do not NULL-terminate but may \n-terminate. | 
 |   // | 
 |   // Belt and suspenders and elastic waistband: First, ensure we | 
 |   // NULL-terminate after nBytesData; this is the inner constructor | 
 |   // with nBytesData as a parameter.  Then, return only up to the | 
 |   // first null in case of embedded NULLs; this is the outer | 
 |   // constructor that takes the result of c_str() on the inner.  If | 
 |   // the server is giving us back a buffer with embedded NULLs, | 
 |   // something is broken anyway.  Finally, trim trailing whitespace. | 
 |   std::string result(std::string(data, count_bytes).c_str()); | 
 |   base::TrimWhitespaceASCII(result, base::TRIM_TRAILING, &result); | 
 |   return result; | 
 | } | 
 |  | 
 | }  // namespace net |