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