Home | History | Annotate | Download | only in proxy
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/logging.h"
     10 #include "base/message_loop/message_loop_proxy.h"
     11 #include "base/metrics/histogram.h"
     12 #include "base/strings/sys_string_conversions.h"
     13 #include "base/threading/worker_pool.h"
     14 #include "base/time/time.h"
     15 #include "net/base/net_errors.h"
     16 #include "net/proxy/dhcpcsvc_init_win.h"
     17 #include "net/proxy/proxy_script_fetcher_impl.h"
     18 #include "net/url_request/url_request_context.h"
     19 
     20 #include <windows.h>
     21 #include <winsock2.h>
     22 #include <dhcpcsdk.h>
     23 #pragma comment(lib, "dhcpcsvc.lib")
     24 
     25 namespace {
     26 
     27 // Maximum amount of time to wait for response from the Win32 DHCP API.
     28 const int kTimeoutMs = 2000;
     29 
     30 }  // namespace
     31 
     32 namespace net {
     33 
     34 DhcpProxyScriptAdapterFetcher::DhcpProxyScriptAdapterFetcher(
     35     URLRequestContext* url_request_context)
     36     : state_(STATE_START),
     37       result_(ERR_IO_PENDING),
     38       url_request_context_(url_request_context) {
     39   DCHECK(url_request_context_);
     40 }
     41 
     42 DhcpProxyScriptAdapterFetcher::~DhcpProxyScriptAdapterFetcher() {
     43   Cancel();
     44 }
     45 
     46 void DhcpProxyScriptAdapterFetcher::Fetch(
     47     const std::string& adapter_name, const CompletionCallback& callback) {
     48   DCHECK(CalledOnValidThread());
     49   DCHECK_EQ(state_, STATE_START);
     50   result_ = ERR_IO_PENDING;
     51   pac_script_ = base::string16();
     52   state_ = STATE_WAIT_DHCP;
     53   callback_ = callback;
     54 
     55   wait_timer_.Start(FROM_HERE, ImplGetTimeout(),
     56                     this, &DhcpProxyScriptAdapterFetcher::OnTimeout);
     57   scoped_refptr<DhcpQuery> dhcp_query(ImplCreateDhcpQuery());
     58   base::WorkerPool::PostTaskAndReply(
     59       FROM_HERE,
     60       base::Bind(
     61           &DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter,
     62           dhcp_query.get(),
     63           adapter_name),
     64       base::Bind(
     65           &DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone,
     66           AsWeakPtr(),
     67           dhcp_query),
     68       true);
     69 }
     70 
     71 void DhcpProxyScriptAdapterFetcher::Cancel() {
     72   DCHECK(CalledOnValidThread());
     73   callback_.Reset();
     74   wait_timer_.Stop();
     75   script_fetcher_.reset();
     76 
     77   switch (state_) {
     78     case STATE_WAIT_DHCP:
     79       // Nothing to do here, we let the worker thread run to completion,
     80       // the task it posts back when it completes will check the state.
     81       break;
     82     case STATE_WAIT_URL:
     83       break;
     84     case STATE_START:
     85     case STATE_FINISH:
     86     case STATE_CANCEL:
     87       break;
     88   }
     89 
     90   if (state_ != STATE_FINISH) {
     91     result_ = ERR_ABORTED;
     92     state_ = STATE_CANCEL;
     93   }
     94 }
     95 
     96 bool DhcpProxyScriptAdapterFetcher::DidFinish() const {
     97   DCHECK(CalledOnValidThread());
     98   return state_ == STATE_FINISH;
     99 }
    100 
    101 int DhcpProxyScriptAdapterFetcher::GetResult() const {
    102   DCHECK(CalledOnValidThread());
    103   return result_;
    104 }
    105 
    106 base::string16 DhcpProxyScriptAdapterFetcher::GetPacScript() const {
    107   DCHECK(CalledOnValidThread());
    108   return pac_script_;
    109 }
    110 
    111 GURL DhcpProxyScriptAdapterFetcher::GetPacURL() const {
    112   DCHECK(CalledOnValidThread());
    113   return pac_url_;
    114 }
    115 
    116 DhcpProxyScriptAdapterFetcher::DhcpQuery::DhcpQuery() {
    117 }
    118 
    119 DhcpProxyScriptAdapterFetcher::DhcpQuery::~DhcpQuery() {
    120 }
    121 
    122 void DhcpProxyScriptAdapterFetcher::DhcpQuery::GetPacURLForAdapter(
    123     const std::string& adapter_name) {
    124   url_ = ImplGetPacURLFromDhcp(adapter_name);
    125 }
    126 
    127 const std::string& DhcpProxyScriptAdapterFetcher::DhcpQuery::url() const {
    128   return url_;
    129 }
    130 
    131 std::string
    132     DhcpProxyScriptAdapterFetcher::DhcpQuery::ImplGetPacURLFromDhcp(
    133         const std::string& adapter_name) {
    134   return DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(adapter_name);
    135 }
    136 
    137 void DhcpProxyScriptAdapterFetcher::OnDhcpQueryDone(
    138     scoped_refptr<DhcpQuery> dhcp_query) {
    139   DCHECK(CalledOnValidThread());
    140   // Because we can't cancel the call to the Win32 API, we can expect
    141   // it to finish while we are in a few different states.  The expected
    142   // one is WAIT_DHCP, but it could be in CANCEL if Cancel() was called,
    143   // or FINISH if timeout occurred.
    144   DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_CANCEL ||
    145          state_ == STATE_FINISH);
    146   if (state_ != STATE_WAIT_DHCP)
    147     return;
    148 
    149   wait_timer_.Stop();
    150 
    151   pac_url_ = GURL(dhcp_query->url());
    152   if (pac_url_.is_empty() || !pac_url_.is_valid()) {
    153     result_ = ERR_PAC_NOT_IN_DHCP;
    154     TransitionToFinish();
    155   } else {
    156     state_ = STATE_WAIT_URL;
    157     script_fetcher_.reset(ImplCreateScriptFetcher());
    158     script_fetcher_->Fetch(
    159         pac_url_, &pac_script_,
    160         base::Bind(&DhcpProxyScriptAdapterFetcher::OnFetcherDone,
    161                    base::Unretained(this)));
    162   }
    163 }
    164 
    165 void DhcpProxyScriptAdapterFetcher::OnTimeout() {
    166   DCHECK_EQ(state_, STATE_WAIT_DHCP);
    167   result_ = ERR_TIMED_OUT;
    168   TransitionToFinish();
    169 }
    170 
    171 void DhcpProxyScriptAdapterFetcher::OnFetcherDone(int result) {
    172   DCHECK(CalledOnValidThread());
    173   DCHECK(state_ == STATE_WAIT_URL || state_ == STATE_CANCEL);
    174   if (state_ == STATE_CANCEL)
    175     return;
    176 
    177   // At this point, pac_script_ has already been written to.
    178   script_fetcher_.reset();
    179   result_ = result;
    180   TransitionToFinish();
    181 }
    182 
    183 void DhcpProxyScriptAdapterFetcher::TransitionToFinish() {
    184   DCHECK(state_ == STATE_WAIT_DHCP || state_ == STATE_WAIT_URL);
    185   state_ = STATE_FINISH;
    186   CompletionCallback callback = callback_;
    187   callback_.Reset();
    188 
    189   // Be careful not to touch any member state after this, as the client
    190   // may delete us during this callback.
    191   callback.Run(result_);
    192 }
    193 
    194 DhcpProxyScriptAdapterFetcher::State
    195     DhcpProxyScriptAdapterFetcher::state() const {
    196   return state_;
    197 }
    198 
    199 ProxyScriptFetcher* DhcpProxyScriptAdapterFetcher::ImplCreateScriptFetcher() {
    200   return new ProxyScriptFetcherImpl(url_request_context_);
    201 }
    202 
    203 DhcpProxyScriptAdapterFetcher::DhcpQuery*
    204     DhcpProxyScriptAdapterFetcher::ImplCreateDhcpQuery() {
    205   return new DhcpQuery();
    206 }
    207 
    208 base::TimeDelta DhcpProxyScriptAdapterFetcher::ImplGetTimeout() const {
    209   return base::TimeDelta::FromMilliseconds(kTimeoutMs);
    210 }
    211 
    212 // static
    213 std::string DhcpProxyScriptAdapterFetcher::GetPacURLFromDhcp(
    214     const std::string& adapter_name) {
    215   EnsureDhcpcsvcInit();
    216 
    217   std::wstring adapter_name_wide = base::SysMultiByteToWide(adapter_name,
    218                                                             CP_ACP);
    219 
    220   DHCPCAPI_PARAMS_ARRAY send_params = { 0, NULL };
    221 
    222   BYTE option_data[] = { 1, 252 };
    223   DHCPCAPI_PARAMS wpad_params = { 0 };
    224   wpad_params.OptionId = 252;
    225   wpad_params.IsVendor = FALSE;  // Surprising, but intentional.
    226 
    227   DHCPCAPI_PARAMS_ARRAY request_params = { 0 };
    228   request_params.nParams = 1;
    229   request_params.Params = &wpad_params;
    230 
    231   // The maximum message size is typically 4096 bytes on Windows per
    232   // http://support.microsoft.com/kb/321592
    233   DWORD result_buffer_size = 4096;
    234   scoped_ptr_malloc<BYTE> result_buffer;
    235   int retry_count = 0;
    236   DWORD res = NO_ERROR;
    237   do {
    238     result_buffer.reset(reinterpret_cast<BYTE*>(malloc(result_buffer_size)));
    239 
    240     // Note that while the DHCPCAPI_REQUEST_SYNCHRONOUS flag seems to indicate
    241     // there might be an asynchronous mode, there seems to be (at least in
    242     // terms of well-documented use of this API) only a synchronous mode, with
    243     // an optional "async notifications later if the option changes" mode.
    244     // Even IE9, which we hope to emulate as IE is the most widely deployed
    245     // previous implementation of the DHCP aspect of WPAD and the only one
    246     // on Windows (Konqueror is the other, on Linux), uses this API with the
    247     // synchronous flag.  There seem to be several Microsoft Knowledge Base
    248     // articles about calls to this function failing when other flags are used
    249     // (e.g. http://support.microsoft.com/kb/885270) so we won't take any
    250     // chances on non-standard, poorly documented usage.
    251     res = ::DhcpRequestParams(DHCPCAPI_REQUEST_SYNCHRONOUS,
    252                               NULL,
    253                               const_cast<LPWSTR>(adapter_name_wide.c_str()),
    254                               NULL,
    255                               send_params, request_params,
    256                               result_buffer.get(), &result_buffer_size,
    257                               NULL);
    258     ++retry_count;
    259   } while (res == ERROR_MORE_DATA && retry_count <= 3);
    260 
    261   if (res != NO_ERROR) {
    262     LOG(INFO) << "Error fetching PAC URL from DHCP: " << res;
    263     UMA_HISTOGRAM_COUNTS("Net.DhcpWpadUnhandledDhcpError", 1);
    264   } else if (wpad_params.nBytesData) {
    265 #ifndef NDEBUG
    266     // The result should be ASCII, not wide character.  Some DHCP
    267     // servers appear to count the trailing NULL in nBytesData, others
    268     // do not.
    269     size_t count_without_null =
    270         strlen(reinterpret_cast<const char*>(wpad_params.Data));
    271     DCHECK(count_without_null == wpad_params.nBytesData ||
    272            count_without_null + 1 == wpad_params.nBytesData);
    273 #endif
    274     // Belt and suspenders: First, ensure we NULL-terminate after
    275     // nBytesData; this is the inner constructor with nBytesData as a
    276     // parameter.  Then, return only up to the first null in case of
    277     // embedded NULLs; this is the outer constructor that takes the
    278     // result of c_str() on the inner.  If the server is giving us
    279     // back a buffer with embedded NULLs, something is broken anyway.
    280     return std::string(
    281         std::string(reinterpret_cast<const char *>(wpad_params.Data),
    282                     wpad_params.nBytesData).c_str());
    283   }
    284 
    285   return "";
    286 }
    287 
    288 }  // namespace net
    289