Home | History | Annotate | Download | only in captive_portal
      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