blob: 4adf47f9b6e8397360b5553d099345ac5d653c83 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/captive_portal/captive_portal_detector.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "components/data_use_measurement/core/data_use_user_data.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/url_request/url_request_status.h"
namespace captive_portal {
const char CaptivePortalDetector::kDefaultURL[] =
"http://www.gstatic.com/generate_204";
CaptivePortalDetector::CaptivePortalDetector(
network::mojom::URLLoaderFactory* loader_factory)
: loader_factory_(loader_factory) {}
CaptivePortalDetector::~CaptivePortalDetector() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void CaptivePortalDetector::DetectCaptivePortal(
const GURL& url,
DetectionCallback detection_callback,
const net::NetworkTrafficAnnotationTag& traffic_annotation) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!FetchingURL());
DCHECK(detection_callback_.is_null());
detection_callback_ = std::move(detection_callback);
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = url;
// Can't safely use net::LOAD_DISABLE_CERT_NETWORK_FETCHES here,
// since then the connection may be reused without checking the cert.
resource_request->load_flags = net::LOAD_BYPASS_CACHE;
resource_request->allow_credentials = false;
// TODO(jam): switch to using ServiceURLLoader to track data measurement once
// https://crbug.com/808498 is fixed.
simple_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
traffic_annotation);
simple_loader_->SetAllowHttpErrorResults(true);
network::SimpleURLLoader::BodyAsStringCallback callback = base::BindOnce(
&CaptivePortalDetector::OnSimpleLoaderComplete, base::Unretained(this));
simple_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
loader_factory_, std::move(callback));
}
void CaptivePortalDetector::Cancel() {
simple_loader_.reset();
detection_callback_.Reset();
}
void CaptivePortalDetector::OnSimpleLoaderComplete(
std::unique_ptr<std::string> response_body) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(FetchingURL());
DCHECK(!detection_callback_.is_null());
int response_code = 0;
net::HttpResponseHeaders* headers = nullptr;
if (simple_loader_->ResponseInfo() &&
simple_loader_->ResponseInfo()->headers) {
headers = simple_loader_->ResponseInfo()->headers.get();
response_code = simple_loader_->ResponseInfo()->headers->response_code();
}
OnSimpleLoaderCompleteInternal(simple_loader_->NetError(), response_code,
simple_loader_->GetFinalURL(), headers);
}
void CaptivePortalDetector::OnSimpleLoaderCompleteInternal(
int net_error,
int response_code,
const GURL& url,
net::HttpResponseHeaders* headers) {
Results results;
GetCaptivePortalResultFromResponse(net_error, response_code, url, headers,
&results);
simple_loader_.reset();
std::move(detection_callback_).Run(results);
}
void CaptivePortalDetector::GetCaptivePortalResultFromResponse(
int net_error,
int response_code,
const GURL& url,
net::HttpResponseHeaders* headers,
Results* results) const {
results->result = captive_portal::RESULT_NO_RESPONSE;
results->response_code = response_code;
results->retry_after_delta = base::TimeDelta();
results->landing_url = url;
VLOG(1) << "Getting captive portal result"
<< " response code: " << results->response_code
<< " landing_url: " << results->landing_url;
// If there's a network error of some sort when fetching a file via HTTP,
// there may be a networking problem, rather than a captive portal.
// TODO(mmenke): Consider special handling for redirects that end up at
// errors, especially SSL certificate errors.
if (net_error != net::OK)
return;
// In the case of 503 errors, look for the Retry-After header.
if (results->response_code == 503) {
std::string retry_after_string;
// If there's no Retry-After header, nothing else to do.
if (!headers->EnumerateHeader(nullptr, "Retry-After", &retry_after_string))
return;
base::TimeDelta retry_after_delta;
if (net::HttpUtil::ParseRetryAfterHeader(
retry_after_string, GetCurrentTime(), &retry_after_delta)) {
results->retry_after_delta = retry_after_delta;
}
return;
}
// A 511 response (Network Authentication Required) means that the user needs
// to login to whatever server issued the response.
// See: http://tools.ietf.org/html/rfc6585
if (results->response_code == 511) {
results->result = captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL;
return;
}
// Other non-2xx/3xx HTTP responses may indicate server errors.
if (results->response_code >= 400 || results->response_code < 200)
return;
// A 204 response code indicates there's no captive portal.
if (results->response_code == 204) {
results->result = captive_portal::RESULT_INTERNET_CONNECTED;
return;
}
// Otherwise, assume it's a captive portal.
results->result = captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL;
}
base::Time CaptivePortalDetector::GetCurrentTime() const {
if (time_for_testing_.is_null())
return base::Time::Now();
return time_for_testing_;
}
bool CaptivePortalDetector::FetchingURL() const {
return simple_loader_ != nullptr;
}
} // namespace captive_portal