// 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/sys_string_conversions.h" #include "base/threading/worker_pool.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 #include #include #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) : 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 dhcp_query(ImplCreateDhcpQuery()); base::WorkerPool::PostTaskAndReply( FROM_HERE, base::Bind( &DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter, dhcp_query.get(), adapter_name), base::Bind( &DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone, AsWeakPtr(), dhcp_query), true); } 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 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_malloc result_buffer; int retry_count = 0; DWORD res = NO_ERROR; do { result_buffer.reset(reinterpret_cast(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(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) { LOG(INFO) << "Error fetching PAC URL from DHCP: " << res; UMA_HISTOGRAM_COUNTS("Net.DhcpWpadUnhandledDhcpError", 1); } else if (wpad_params.nBytesData) { #ifndef NDEBUG // The result should be ASCII, not wide character. Some DHCP // servers appear to count the trailing NULL in nBytesData, others // do not. size_t count_without_null = strlen(reinterpret_cast(wpad_params.Data)); DCHECK(count_without_null == wpad_params.nBytesData || count_without_null + 1 == wpad_params.nBytesData); #endif // Belt and suspenders: 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. return std::string( std::string(reinterpret_cast(wpad_params.Data), wpad_params.nBytesData).c_str()); } return ""; } } // namespace net