| // 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/dhcp_proxy_script_adapter_fetcher_win.h" |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/logging.h" |
| #include "base/message_loop/message_loop_proxy.h" |
| #include "base/metrics/histogram.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/task_runner.h" |
| #include "base/time/time.h" |
| #include "net/base/net_errors.h" |
| #include "net/proxy/dhcpcsvc_init_win.h" |
| #include "net/proxy/proxy_script_fetcher_impl.h" |
| #include "net/url_request/url_request_context.h" |
| |
| #include <windows.h> |
| #include <winsock2.h> |
| #include <dhcpcsdk.h> |
| #pragma comment(lib, "dhcpcsvc.lib") |
| |
| namespace { |
| |
| // Maximum amount of time to wait for response from the Win32 DHCP API. |
| const int kTimeoutMs = 2000; |
| |
| } // namespace |
| |
| namespace net { |
| |
| DhcpProxyScriptAdapterFetcher::DhcpProxyScriptAdapterFetcher( |
| 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_); |
| } |
| |
| DhcpProxyScriptAdapterFetcher::~DhcpProxyScriptAdapterFetcher() { |
| Cancel(); |
| } |
| |
| void DhcpProxyScriptAdapterFetcher::Fetch( |
| const std::string& adapter_name, const CompletionCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(state_, STATE_START); |
| result_ = ERR_IO_PENDING; |
| pac_script_ = base::string16(); |
| state_ = STATE_WAIT_DHCP; |
| callback_ = callback; |
| |
| wait_timer_.Start(FROM_HERE, ImplGetTimeout(), |
| this, &DhcpProxyScriptAdapterFetcher::OnTimeout); |
| scoped_refptr<DhcpQuery> dhcp_query(ImplCreateDhcpQuery()); |
| task_runner_->PostTaskAndReply( |
| FROM_HERE, |
| base::Bind( |
| &DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter, |
| dhcp_query.get(), |
| adapter_name), |
| base::Bind( |
| &DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone, |
| AsWeakPtr(), |
| dhcp_query)); |
| } |
| |
| void DhcpProxyScriptAdapterFetcher::Cancel() { |
| DCHECK(CalledOnValidThread()); |
| 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 DhcpProxyScriptAdapterFetcher::DidFinish() const { |
| DCHECK(CalledOnValidThread()); |
| return state_ == STATE_FINISH; |
| } |
| |
| int DhcpProxyScriptAdapterFetcher::GetResult() const { |
| DCHECK(CalledOnValidThread()); |
| return result_; |
| } |
| |
| base::string16 DhcpProxyScriptAdapterFetcher::GetPacScript() const { |
| DCHECK(CalledOnValidThread()); |
| return pac_script_; |
| } |
| |
| GURL DhcpProxyScriptAdapterFetcher::GetPacURL() const { |
| DCHECK(CalledOnValidThread()); |
| return pac_url_; |
| } |
| |
| DhcpProxyScriptAdapterFetcher::DhcpQuery::DhcpQuery() { |
| } |
| |
| DhcpProxyScriptAdapterFetcher::DhcpQuery::~DhcpQuery() { |
| } |
| |
| void DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter( |
| const std::string& adapter_name) { |
| url_ = ImplGetPacURLFromDhcp(adapter_name); |
| } |
| |
| const std::string& DhcpProxyScriptAdapterFetcher::DhcpQuery::url() const { |
| return url_; |
| } |
| |
| std::string |
| DhcpProxyScriptAdapterFetcher::DhcpQuery::ImplGetPacURLFromDhcp( |
| const std::string& adapter_name) { |
| return DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name); |
| } |
| |
| void DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone( |
| scoped_refptr<DhcpQuery> dhcp_query) { |
| DCHECK(CalledOnValidThread()); |
| // 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_.reset(ImplCreateScriptFetcher()); |
| script_fetcher_->Fetch( |
| pac_url_, &pac_script_, |
| base::Bind(&DhcpProxyScriptAdapterFetcher::OnFetcherDone, |
| base::Unretained(this))); |
| } |
| } |
| |
| void DhcpProxyScriptAdapterFetcher::OnTimeout() { |
| DCHECK_EQ(state_, STATE_WAIT_DHCP); |
| result_ = ERR_TIMED_OUT; |
| TransitionToFinish(); |
| } |
| |
| void DhcpProxyScriptAdapterFetcher::OnFetcherDone(int result) { |
| DCHECK(CalledOnValidThread()); |
| 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 DhcpProxyScriptAdapterFetcher::TransitionToFinish() { |
| DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_WAIT_URL); |
| state_ = STATE_FINISH; |
| CompletionCallback callback = callback_; |
| callback_.Reset(); |
| |
| // Be careful not to touch any member state after this, as the client |
| // may delete us during this callback. |
| callback.Run(result_); |
| } |
| |
| DhcpProxyScriptAdapterFetcher::State |
| DhcpProxyScriptAdapterFetcher::state() const { |
| return state_; |
| } |
| |
| ProxyScriptFetcher* DhcpProxyScriptAdapterFetcher::ImplCreateScriptFetcher() { |
| return new ProxyScriptFetcherImpl(url_request_context_); |
| } |
| |
| DhcpProxyScriptAdapterFetcher::DhcpQuery* |
| DhcpProxyScriptAdapterFetcher::ImplCreateDhcpQuery() { |
| return new DhcpQuery(); |
| } |
| |
| base::TimeDelta DhcpProxyScriptAdapterFetcher::ImplGetTimeout() const { |
| return base::TimeDelta::FromMilliseconds(kTimeoutMs); |
| } |
| |
| // static |
| std::string DhcpProxyScriptAdapterFetcher::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 }; |
| |
| BYTE option_data[] = { 1, 252 }; |
| 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; |
| scoped_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. |
| 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; |
| UMA_HISTOGRAM_COUNTS("Net.DhcpWpadUnhandledDhcpError", 1); |
| } else if (wpad_params.nBytesData) { |
| return SanitizeDhcpApiString( |
| reinterpret_cast<const char*>(wpad_params.Data), |
| wpad_params.nBytesData); |
| } |
| |
| return ""; |
| } |
| |
| // static |
| std::string DhcpProxyScriptAdapterFetcher::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 |