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/proxy_script_decider.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/compiler_specific.h"
10#include "base/format_macros.h"
11#include "base/logging.h"
12#include "base/strings/string_util.h"
13#include "base/strings/utf_string_conversions.h"
14#include "base/values.h"
15#include "net/base/net_errors.h"
16#include "net/proxy/dhcp_proxy_script_fetcher.h"
17#include "net/proxy/dhcp_proxy_script_fetcher_factory.h"
18#include "net/proxy/proxy_script_fetcher.h"
19
20namespace net {
21
22namespace {
23
24bool LooksLikePacScript(const base::string16& script) {
25  // Note: this is only an approximation! It may not always work correctly,
26  // however it is very likely that legitimate scripts have this exact string,
27  // since they must minimally define a function of this name. Conversely, a
28  // file not containing the string is not likely to be a PAC script.
29  //
30  // An exact test would have to load the script in a javascript evaluator.
31  return script.find(ASCIIToUTF16("FindProxyForURL")) != base::string16::npos;
32}
33
34}
35
36// This is the hard-coded location used by the DNS portion of web proxy
37// auto-discovery.
38//
39// Note that we not use DNS devolution to find the WPAD host, since that could
40// be dangerous should our top level domain registry  become out of date.
41//
42// Instead we directly resolve "wpad", and let the operating system apply the
43// DNS suffix search paths. This is the same approach taken by Firefox, and
44// compatibility hasn't been an issue.
45//
46// For more details, also check out this comment:
47// http://code.google.com/p/chromium/issues/detail?id=18575#c20
48static const char kWpadUrl[] = "http://wpad/wpad.dat";
49
50base::Value* ProxyScriptDecider::PacSource::NetLogCallback(
51    const GURL* effective_pac_url,
52    NetLog::LogLevel /* log_level */) const {
53  base::DictionaryValue* dict = new base::DictionaryValue();
54  std::string source;
55  switch (type) {
56    case PacSource::WPAD_DHCP:
57      source = "WPAD DHCP";
58      break;
59    case PacSource::WPAD_DNS:
60      source = "WPAD DNS: ";
61      source += effective_pac_url->possibly_invalid_spec();
62      break;
63    case PacSource::CUSTOM:
64      source = "Custom PAC URL: ";
65      source += effective_pac_url->possibly_invalid_spec();
66      break;
67  }
68  dict->SetString("source", source);
69  return dict;
70}
71
72ProxyScriptDecider::ProxyScriptDecider(
73    ProxyScriptFetcher* proxy_script_fetcher,
74    DhcpProxyScriptFetcher* dhcp_proxy_script_fetcher,
75    NetLog* net_log)
76    : resolver_(NULL),
77      proxy_script_fetcher_(proxy_script_fetcher),
78      dhcp_proxy_script_fetcher_(dhcp_proxy_script_fetcher),
79      current_pac_source_index_(0u),
80      pac_mandatory_(false),
81      next_state_(STATE_NONE),
82      net_log_(BoundNetLog::Make(
83          net_log, NetLog::SOURCE_PROXY_SCRIPT_DECIDER)),
84      fetch_pac_bytes_(false) {
85}
86
87ProxyScriptDecider::~ProxyScriptDecider() {
88  if (next_state_ != STATE_NONE)
89    Cancel();
90}
91
92int ProxyScriptDecider::Start(
93    const ProxyConfig& config, const base::TimeDelta wait_delay,
94    bool fetch_pac_bytes, const CompletionCallback& callback) {
95  DCHECK_EQ(STATE_NONE, next_state_);
96  DCHECK(!callback.is_null());
97  DCHECK(config.HasAutomaticSettings());
98
99  net_log_.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER);
100
101  fetch_pac_bytes_ = fetch_pac_bytes;
102
103  // Save the |wait_delay| as a non-negative value.
104  wait_delay_ = wait_delay;
105  if (wait_delay_ < base::TimeDelta())
106    wait_delay_ = base::TimeDelta();
107
108  pac_mandatory_ = config.pac_mandatory();
109
110  pac_sources_ = BuildPacSourcesFallbackList(config);
111  DCHECK(!pac_sources_.empty());
112
113  next_state_ = STATE_WAIT;
114
115  int rv = DoLoop(OK);
116  if (rv == ERR_IO_PENDING)
117    callback_ = callback;
118  else
119    DidComplete();
120
121  return rv;
122}
123
124const ProxyConfig& ProxyScriptDecider::effective_config() const {
125  DCHECK_EQ(STATE_NONE, next_state_);
126  return effective_config_;
127}
128
129// TODO(eroman): Return a const-pointer.
130ProxyResolverScriptData* ProxyScriptDecider::script_data() const {
131  DCHECK_EQ(STATE_NONE, next_state_);
132  return script_data_.get();
133}
134
135// Initialize the fallback rules.
136// (1) WPAD (DHCP).
137// (2) WPAD (DNS).
138// (3) Custom PAC URL.
139ProxyScriptDecider::PacSourceList ProxyScriptDecider::
140    BuildPacSourcesFallbackList(
141    const ProxyConfig& config) const {
142  PacSourceList pac_sources;
143  if (config.auto_detect()) {
144    pac_sources.push_back(PacSource(PacSource::WPAD_DHCP, GURL()));
145    pac_sources.push_back(PacSource(PacSource::WPAD_DNS, GURL()));
146  }
147  if (config.has_pac_url())
148    pac_sources.push_back(PacSource(PacSource::CUSTOM, config.pac_url()));
149  return pac_sources;
150}
151
152void ProxyScriptDecider::OnIOCompletion(int result) {
153  DCHECK_NE(STATE_NONE, next_state_);
154  int rv = DoLoop(result);
155  if (rv != ERR_IO_PENDING) {
156    DidComplete();
157    DoCallback(rv);
158  }
159}
160
161int ProxyScriptDecider::DoLoop(int result) {
162  DCHECK_NE(next_state_, STATE_NONE);
163  int rv = result;
164  do {
165    State state = next_state_;
166    next_state_ = STATE_NONE;
167    switch (state) {
168      case STATE_WAIT:
169        DCHECK_EQ(OK, rv);
170        rv = DoWait();
171        break;
172      case STATE_WAIT_COMPLETE:
173        rv = DoWaitComplete(rv);
174        break;
175      case STATE_FETCH_PAC_SCRIPT:
176        DCHECK_EQ(OK, rv);
177        rv = DoFetchPacScript();
178        break;
179      case STATE_FETCH_PAC_SCRIPT_COMPLETE:
180        rv = DoFetchPacScriptComplete(rv);
181        break;
182      case STATE_VERIFY_PAC_SCRIPT:
183        DCHECK_EQ(OK, rv);
184        rv = DoVerifyPacScript();
185        break;
186      case STATE_VERIFY_PAC_SCRIPT_COMPLETE:
187        rv = DoVerifyPacScriptComplete(rv);
188        break;
189      default:
190        NOTREACHED() << "bad state";
191        rv = ERR_UNEXPECTED;
192        break;
193    }
194  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
195  return rv;
196}
197
198void ProxyScriptDecider::DoCallback(int result) {
199  DCHECK_NE(ERR_IO_PENDING, result);
200  DCHECK(!callback_.is_null());
201  callback_.Run(result);
202}
203
204int ProxyScriptDecider::DoWait() {
205  next_state_ = STATE_WAIT_COMPLETE;
206
207  // If no waiting is required, continue on to the next state.
208  if (wait_delay_.ToInternalValue() == 0)
209    return OK;
210
211  // Otherwise wait the specified amount of time.
212  wait_timer_.Start(FROM_HERE, wait_delay_, this,
213                    &ProxyScriptDecider::OnWaitTimerFired);
214  net_log_.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_WAIT);
215  return ERR_IO_PENDING;
216}
217
218int ProxyScriptDecider::DoWaitComplete(int result) {
219  DCHECK_EQ(OK, result);
220  if (wait_delay_.ToInternalValue() != 0) {
221    net_log_.EndEventWithNetErrorCode(NetLog::TYPE_PROXY_SCRIPT_DECIDER_WAIT,
222                                      result);
223  }
224  next_state_ = GetStartState();
225  return OK;
226}
227
228int ProxyScriptDecider::DoFetchPacScript() {
229  DCHECK(fetch_pac_bytes_);
230
231  next_state_ = STATE_FETCH_PAC_SCRIPT_COMPLETE;
232
233  const PacSource& pac_source = current_pac_source();
234
235  GURL effective_pac_url;
236  DetermineURL(pac_source, &effective_pac_url);
237
238  net_log_.BeginEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT,
239                      base::Bind(&PacSource::NetLogCallback,
240                                 base::Unretained(&pac_source),
241                                 &effective_pac_url));
242
243  if (pac_source.type == PacSource::WPAD_DHCP) {
244    if (!dhcp_proxy_script_fetcher_) {
245      net_log_.AddEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_HAS_NO_FETCHER);
246      return ERR_UNEXPECTED;
247    }
248
249    return dhcp_proxy_script_fetcher_->Fetch(
250        &pac_script_, base::Bind(&ProxyScriptDecider::OnIOCompletion,
251                                 base::Unretained(this)));
252  }
253
254  if (!proxy_script_fetcher_) {
255    net_log_.AddEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER_HAS_NO_FETCHER);
256    return ERR_UNEXPECTED;
257  }
258
259  return proxy_script_fetcher_->Fetch(
260      effective_pac_url, &pac_script_,
261      base::Bind(&ProxyScriptDecider::OnIOCompletion, base::Unretained(this)));
262}
263
264int ProxyScriptDecider::DoFetchPacScriptComplete(int result) {
265  DCHECK(fetch_pac_bytes_);
266
267  net_log_.EndEventWithNetErrorCode(
268      NetLog::TYPE_PROXY_SCRIPT_DECIDER_FETCH_PAC_SCRIPT, result);
269  if (result != OK)
270    return TryToFallbackPacSource(result);
271
272  next_state_ = STATE_VERIFY_PAC_SCRIPT;
273  return result;
274}
275
276int ProxyScriptDecider::DoVerifyPacScript() {
277  next_state_ = STATE_VERIFY_PAC_SCRIPT_COMPLETE;
278
279  // This is just a heuristic. Ideally we would try to parse the script.
280  if (fetch_pac_bytes_ && !LooksLikePacScript(pac_script_))
281    return ERR_PAC_SCRIPT_FAILED;
282
283  return OK;
284}
285
286int ProxyScriptDecider::DoVerifyPacScriptComplete(int result) {
287  if (result != OK)
288    return TryToFallbackPacSource(result);
289
290  const PacSource& pac_source = current_pac_source();
291
292  // Extract the current script data.
293  if (fetch_pac_bytes_) {
294    script_data_ = ProxyResolverScriptData::FromUTF16(pac_script_);
295  } else {
296    script_data_ = pac_source.type == PacSource::CUSTOM ?
297        ProxyResolverScriptData::FromURL(pac_source.url) :
298        ProxyResolverScriptData::ForAutoDetect();
299  }
300
301  // Let the caller know which automatic setting we ended up initializing the
302  // resolver for (there may have been multiple fallbacks to choose from.)
303  if (current_pac_source().type == PacSource::CUSTOM) {
304    effective_config_ =
305        ProxyConfig::CreateFromCustomPacURL(current_pac_source().url);
306    effective_config_.set_pac_mandatory(pac_mandatory_);
307  } else {
308    if (fetch_pac_bytes_) {
309      GURL auto_detected_url;
310
311      switch (current_pac_source().type) {
312        case PacSource::WPAD_DHCP:
313          auto_detected_url = dhcp_proxy_script_fetcher_->GetPacURL();
314          break;
315
316        case PacSource::WPAD_DNS:
317          auto_detected_url = GURL(kWpadUrl);
318          break;
319
320        default:
321          NOTREACHED();
322      }
323
324      effective_config_ =
325          ProxyConfig::CreateFromCustomPacURL(auto_detected_url);
326    } else {
327      // The resolver does its own resolution so we cannot know the
328      // URL. Just do the best we can and state that the configuration
329      // is to auto-detect proxy settings.
330      effective_config_ = ProxyConfig::CreateAutoDetect();
331    }
332  }
333
334  return OK;
335}
336
337int ProxyScriptDecider::TryToFallbackPacSource(int error) {
338  DCHECK_LT(error, 0);
339
340  if (current_pac_source_index_ + 1 >= pac_sources_.size()) {
341    // Nothing left to fall back to.
342    return error;
343  }
344
345  // Advance to next URL in our list.
346  ++current_pac_source_index_;
347
348  net_log_.AddEvent(
349      NetLog::TYPE_PROXY_SCRIPT_DECIDER_FALLING_BACK_TO_NEXT_PAC_SOURCE);
350
351  next_state_ = GetStartState();
352
353  return OK;
354}
355
356ProxyScriptDecider::State ProxyScriptDecider::GetStartState() const {
357  return fetch_pac_bytes_ ?  STATE_FETCH_PAC_SCRIPT : STATE_VERIFY_PAC_SCRIPT;
358}
359
360void ProxyScriptDecider::DetermineURL(const PacSource& pac_source,
361                                      GURL* effective_pac_url) {
362  DCHECK(effective_pac_url);
363
364  switch (pac_source.type) {
365    case PacSource::WPAD_DHCP:
366      break;
367    case PacSource::WPAD_DNS:
368      *effective_pac_url = GURL(kWpadUrl);
369      break;
370    case PacSource::CUSTOM:
371      *effective_pac_url = pac_source.url;
372      break;
373  }
374}
375
376const ProxyScriptDecider::PacSource&
377    ProxyScriptDecider::current_pac_source() const {
378  DCHECK_LT(current_pac_source_index_, pac_sources_.size());
379  return pac_sources_[current_pac_source_index_];
380}
381
382void ProxyScriptDecider::OnWaitTimerFired() {
383  OnIOCompletion(OK);
384}
385
386void ProxyScriptDecider::DidComplete() {
387  net_log_.EndEvent(NetLog::TYPE_PROXY_SCRIPT_DECIDER);
388}
389
390void ProxyScriptDecider::Cancel() {
391  DCHECK_NE(STATE_NONE, next_state_);
392
393  net_log_.AddEvent(NetLog::TYPE_CANCELLED);
394
395  switch (next_state_) {
396    case STATE_WAIT_COMPLETE:
397      wait_timer_.Stop();
398      break;
399    case STATE_FETCH_PAC_SCRIPT_COMPLETE:
400      proxy_script_fetcher_->Cancel();
401      break;
402    default:
403      NOTREACHED();
404      break;
405  }
406
407  // This is safe to call in any state.
408  if (dhcp_proxy_script_fetcher_)
409    dhcp_proxy_script_fetcher_->Cancel();
410
411  DidComplete();
412}
413
414}  // namespace net
415