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