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 "components/captive_portal/captive_portal_detector.h" 6 7 #include "base/logging.h" 8 #include "base/strings/string_number_conversions.h" 9 #include "net/base/load_flags.h" 10 #include "net/http/http_response_headers.h" 11 #include "net/url_request/url_request_status.h" 12 13 namespace captive_portal { 14 15 const char CaptivePortalDetector::kDefaultURL[] = 16 "http://www.gstatic.com/generate_204"; 17 18 CaptivePortalDetector::CaptivePortalDetector( 19 const scoped_refptr<net::URLRequestContextGetter>& request_context) 20 : request_context_(request_context) { 21 } 22 23 CaptivePortalDetector::~CaptivePortalDetector() { 24 } 25 26 void CaptivePortalDetector::DetectCaptivePortal( 27 const GURL& url, 28 const DetectionCallback& detection_callback) { 29 DCHECK(CalledOnValidThread()); 30 DCHECK(!FetchingURL()); 31 DCHECK(detection_callback_.is_null()); 32 33 detection_callback_ = detection_callback; 34 35 // The first 0 means this can use a TestURLFetcherFactory in unit tests. 36 url_fetcher_.reset(net::URLFetcher::Create(0, 37 url, 38 net::URLFetcher::GET, 39 this)); 40 url_fetcher_->SetAutomaticallyRetryOn5xx(false); 41 url_fetcher_->SetRequestContext(request_context_.get()); 42 43 // Can't safely use net::LOAD_DISABLE_CERT_REVOCATION_CHECKING here, 44 // since then the connection may be reused without checking the cert. 45 url_fetcher_->SetLoadFlags( 46 net::LOAD_BYPASS_CACHE | 47 net::LOAD_DO_NOT_PROMPT_FOR_LOGIN | 48 net::LOAD_DO_NOT_SAVE_COOKIES | 49 net::LOAD_DO_NOT_SEND_COOKIES | 50 net::LOAD_DO_NOT_SEND_AUTH_DATA); 51 url_fetcher_->Start(); 52 } 53 54 void CaptivePortalDetector::Cancel() { 55 url_fetcher_.reset(); 56 detection_callback_.Reset(); 57 } 58 59 void CaptivePortalDetector::OnURLFetchComplete(const net::URLFetcher* source) { 60 DCHECK(CalledOnValidThread()); 61 DCHECK(FetchingURL()); 62 DCHECK_EQ(url_fetcher_.get(), source); 63 DCHECK(!detection_callback_.is_null()); 64 65 Results results; 66 GetCaptivePortalResultFromResponse(url_fetcher_.get(), &results); 67 DetectionCallback callback = detection_callback_; 68 url_fetcher_.reset(); 69 detection_callback_.Reset(); 70 callback.Run(results); 71 } 72 73 // Takes a net::URLFetcher that has finished trying to retrieve the test 74 // URL, and returns a CaptivePortalService::Result based on its result. 75 void CaptivePortalDetector::GetCaptivePortalResultFromResponse( 76 const net::URLFetcher* url_fetcher, 77 Results* results) const { 78 DCHECK(results); 79 DCHECK(!url_fetcher->GetStatus().is_io_pending()); 80 81 results->result = captive_portal::RESULT_NO_RESPONSE; 82 results->response_code = url_fetcher->GetResponseCode(); 83 results->retry_after_delta = base::TimeDelta(); 84 results->landing_url = url_fetcher->GetURL(); 85 86 // If there's a network error of some sort when fetching a file via HTTP, 87 // there may be a networking problem, rather than a captive portal. 88 // TODO(mmenke): Consider special handling for redirects that end up at 89 // errors, especially SSL certificate errors. 90 if (url_fetcher->GetStatus().status() != net::URLRequestStatus::SUCCESS) 91 return; 92 93 // In the case of 503 errors, look for the Retry-After header. 94 if (results->response_code == 503) { 95 net::HttpResponseHeaders* headers = url_fetcher->GetResponseHeaders(); 96 std::string retry_after_string; 97 98 // If there's no Retry-After header, nothing else to do. 99 if (!headers->EnumerateHeader(NULL, "Retry-After", &retry_after_string)) 100 return; 101 102 // Otherwise, try parsing it as an integer (seconds) or as an HTTP date. 103 int seconds; 104 base::Time full_date; 105 if (base::StringToInt(retry_after_string, &seconds)) { 106 results->retry_after_delta = base::TimeDelta::FromSeconds(seconds); 107 } else if (headers->GetTimeValuedHeader("Retry-After", &full_date)) { 108 base::Time now = GetCurrentTime(); 109 if (full_date > now) 110 results->retry_after_delta = full_date - now; 111 } 112 return; 113 } 114 115 // A 511 response (Network Authentication Required) means that the user needs 116 // to login to whatever server issued the response. 117 // See: http://tools.ietf.org/html/rfc6585 118 if (results->response_code == 511) { 119 results->result = captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL; 120 return; 121 } 122 123 // Other non-2xx/3xx HTTP responses may indicate server errors. 124 if (results->response_code >= 400 || results->response_code < 200) 125 return; 126 127 // A 204 response code indicates there's no captive portal. 128 if (results->response_code == 204) { 129 results->result = captive_portal::RESULT_INTERNET_CONNECTED; 130 return; 131 } 132 133 // Otherwise, assume it's a captive portal. 134 results->result = captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL; 135 } 136 137 base::Time CaptivePortalDetector::GetCurrentTime() const { 138 if (time_for_testing_.is_null()) 139 return base::Time::Now(); 140 else 141 return time_for_testing_; 142 } 143 144 bool CaptivePortalDetector::FetchingURL() const { 145 return url_fetcher_.get() != NULL; 146 } 147 148 } // namespace captive_portal 149