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