|  | // 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> | 
|  |  | 
|  | #include "starboard/memory.h" | 
|  | #include "starboard/types.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, NULL }; | 
|  |  | 
|  | 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*>(SbMemoryAllocate(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( | 
|  | base::BlockingType::MAY_BLOCK); | 
|  | res = ::DhcpRequestParams(DHCPCAPI_REQUEST_SYNCHRONOUS, | 
|  | NULL, | 
|  | const_cast<LPWSTR>(adapter_name_wide.c_str()), | 
|  | NULL, | 
|  | send_params, request_params, | 
|  | result_buffer.get(), &result_buffer_size, | 
|  | NULL); | 
|  | ++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 |