Index: net/proxy/dhcp_proxy_script_adapter_fetcher_win.cc |
diff --git a/net/proxy/dhcp_proxy_script_adapter_fetcher_win.cc b/net/proxy/dhcp_proxy_script_adapter_fetcher_win.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f09537c256a68e83d8cc264be727e4b22f426e93 |
--- /dev/null |
+++ b/net/proxy/dhcp_proxy_script_adapter_fetcher_win.cc |
@@ -0,0 +1,286 @@ |
+// Copyright (c) 2011 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/message_loop_proxy.h" |
+#include "base/sys_string_conversions.h" |
+#include "base/task.h" |
+#include "base/threading/worker_pool.h" |
+#include "base/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) |
+ : state_(STATE_START), |
+ result_(ERR_IO_PENDING), |
+ callback_(NULL), |
+ ALLOW_THIS_IN_INITIALIZER_LIST( |
+ script_fetcher_callback_( |
+ this, &DhcpProxyScriptAdapterFetcher::OnFetcherDone)), |
+ url_request_context_(url_request_context) { |
+} |
+ |
+DhcpProxyScriptAdapterFetcher::~DhcpProxyScriptAdapterFetcher() { |
+ Cancel(); |
+ |
+ // The WeakPtr we passed to the worker thread may be destroyed on the |
+ // worker thread. This detaches any outstanding WeakPtr state from |
+ // the current thread. |
+ base::SupportsWeakPtr<DhcpProxyScriptAdapterFetcher>::DetachFromThread(); |
+} |
+ |
+void DhcpProxyScriptAdapterFetcher::Fetch( |
+ const std::string& adapter_name, CompletionCallback* callback) { |
+ DCHECK(CalledOnValidThread()); |
+ DCHECK_EQ(state_, STATE_START); |
+ result_ = ERR_IO_PENDING; |
+ pac_script_ = string16(); |
+ state_ = STATE_WAIT_DHCP; |
+ callback_ = callback; |
+ |
+ wait_timer_.Start(ImplGetTimeout(), |
+ this, &DhcpProxyScriptAdapterFetcher::OnTimeout); |
+ worker_thread_ = ImplCreateWorkerThread(AsWeakPtr()); |
+ worker_thread_->Start(adapter_name); |
+} |
+ |
+void DhcpProxyScriptAdapterFetcher::Cancel() { |
+ DCHECK(CalledOnValidThread()); |
+ callback_ = NULL; |
+ 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_; |
+} |
+ |
+string16 DhcpProxyScriptAdapterFetcher::GetPacScript() const { |
+ DCHECK(CalledOnValidThread()); |
+ return pac_script_; |
+} |
+ |
+GURL DhcpProxyScriptAdapterFetcher::GetPacURL() const { |
+ DCHECK(CalledOnValidThread()); |
+ return pac_url_; |
+} |
+ |
+DhcpProxyScriptAdapterFetcher::WorkerThread::WorkerThread( |
+ const base::WeakPtr<DhcpProxyScriptAdapterFetcher>& owner) |
+ : owner_(owner), |
+ origin_loop_(base::MessageLoopProxy::CreateForCurrentThread()) { |
+} |
+ |
+DhcpProxyScriptAdapterFetcher::WorkerThread::~WorkerThread() { |
+} |
+ |
+void DhcpProxyScriptAdapterFetcher::WorkerThread::Start( |
+ const std::string& adapter_name) { |
+ bool succeeded = base::WorkerPool::PostTask( |
+ FROM_HERE, |
+ NewRunnableMethod( |
+ this, |
+ &DhcpProxyScriptAdapterFetcher::WorkerThread::ThreadFunc, |
+ adapter_name), |
+ true); |
+ DCHECK(succeeded); |
+} |
+ |
+void DhcpProxyScriptAdapterFetcher::WorkerThread::ThreadFunc( |
+ const std::string& adapter_name) { |
+ std::string url = ImplGetPacURLFromDhcp(adapter_name); |
+ |
+ bool succeeded = origin_loop_->PostTask( |
+ FROM_HERE, |
+ NewRunnableMethod( |
+ this, |
+ &DhcpProxyScriptAdapterFetcher::WorkerThread::OnThreadDone, |
+ url)); |
+ DCHECK(succeeded); |
+} |
+ |
+void DhcpProxyScriptAdapterFetcher::WorkerThread::OnThreadDone( |
+ const std::string& url) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ if (owner_) |
+ owner_->OnQueryDhcpDone(url); |
+} |
+ |
+std::string |
+ DhcpProxyScriptAdapterFetcher::WorkerThread::ImplGetPacURLFromDhcp( |
+ const std::string& adapter_name) { |
+ return DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name); |
+} |
+ |
+void DhcpProxyScriptAdapterFetcher::OnQueryDhcpDone( |
+ const std::string& url) { |
+ 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(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_, &script_fetcher_callback_); |
+ } |
+} |
+ |
+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; |
+ callback_->Run(result_); |
+ callback_ = NULL; |
+} |
+ |
+ProxyScriptFetcher* DhcpProxyScriptAdapterFetcher::ImplCreateScriptFetcher() { |
+ return new ProxyScriptFetcherImpl(url_request_context_); |
+} |
+ |
+DhcpProxyScriptAdapterFetcher::WorkerThread* |
+ DhcpProxyScriptAdapterFetcher::ImplCreateWorkerThread( |
+ const base::WeakPtr<DhcpProxyScriptAdapterFetcher>& owner) { |
+ return new WorkerThread(owner); |
+} |
+ |
+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<BYTE> result_buffer; |
+ int retry_count = 0; |
+ DWORD res = NO_ERROR; |
+ do { |
+ result_buffer.reset(reinterpret_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) { |
+ NOTREACHED(); |
+ } else if (wpad_params.nBytesData) { |
+ // The result should be ASCII, not wide character. |
+ DCHECK_EQ(strlen(reinterpret_cast<const char*>(wpad_params.Data)) + 1, |
+ wpad_params.nBytesData); |
+ // Return only up to the first null in case of embedded NULLs; if the |
+ // server is giving us back a buffer with embedded NULLs, something is |
+ // broken anyway. |
+ return std::string(reinterpret_cast<const char *>(wpad_params.Data)); |
+ } |
+ |
+ return ""; |
+} |
+ |
+} // namespace net |