| // 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 |