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