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