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