| // Copyright 2014 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/domain_reliability/util.h" | |
| #include <stddef.h> | |
| #include "base/callback.h" | |
| #include "base/logging.h" | |
| #include "base/memory/weak_ptr.h" | |
| #include "base/stl_util.h" | |
| #include "base/time/time.h" | |
| #include "base/timer/timer.h" | |
| #include "net/base/net_errors.h" | |
| namespace domain_reliability { | |
| namespace { | |
| const struct NetErrorMapping { | |
| int net_error; | |
| const char* beacon_status; | |
| } net_error_map[] = { | |
| { net::OK, "ok" }, | |
| { net::ERR_ABORTED, "aborted" }, | |
| { net::ERR_TIMED_OUT, "tcp.connection.timed_out" }, | |
| { net::ERR_CONNECTION_CLOSED, "tcp.connection.closed" }, | |
| { net::ERR_CONNECTION_RESET, "tcp.connection.reset" }, | |
| { net::ERR_CONNECTION_REFUSED, "tcp.connection.refused" }, | |
| { net::ERR_CONNECTION_ABORTED, "tcp.connection.aborted" }, | |
| { net::ERR_CONNECTION_FAILED, "tcp.connection.failed" }, | |
| { net::ERR_NAME_NOT_RESOLVED, "dns" }, | |
| { net::ERR_SSL_PROTOCOL_ERROR, "ssl.protocol.error" }, | |
| { net::ERR_ADDRESS_INVALID, "tcp.connection.address_invalid" }, | |
| { net::ERR_ADDRESS_UNREACHABLE, "tcp.connection.address_unreachable" }, | |
| { net::ERR_CONNECTION_TIMED_OUT, "tcp.connection.timed_out" }, | |
| { net::ERR_NAME_RESOLUTION_FAILED, "dns" }, | |
| { net::ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN, | |
| "ssl.cert.pinned_key_not_in_cert_chain" }, | |
| { net::ERR_CERT_COMMON_NAME_INVALID, "ssl.cert.name_invalid" }, | |
| { net::ERR_CERT_DATE_INVALID, "ssl.cert.date_invalid" }, | |
| { net::ERR_CERT_AUTHORITY_INVALID, "ssl.cert.authority_invalid" }, | |
| { net::ERR_CERT_REVOKED, "ssl.cert.revoked" }, | |
| { net::ERR_CERT_INVALID, "ssl.cert.invalid" }, | |
| { net::ERR_EMPTY_RESPONSE, "http.response.empty" }, | |
| { net::ERR_SPDY_PING_FAILED, "spdy.ping_failed" }, | |
| { net::ERR_SPDY_PROTOCOL_ERROR, "spdy.protocol" }, | |
| { net::ERR_QUIC_PROTOCOL_ERROR, "quic.protocol" }, | |
| { net::ERR_DNS_MALFORMED_RESPONSE, "dns.protocol" }, | |
| { net::ERR_DNS_SERVER_FAILED, "dns.server" }, | |
| { net::ERR_DNS_TIMED_OUT, "dns.timed_out" }, | |
| { net::ERR_INSECURE_RESPONSE, "ssl" }, | |
| { net::ERR_CONTENT_LENGTH_MISMATCH, "http.response.content_length_mismatch" }, | |
| { net::ERR_INCOMPLETE_CHUNKED_ENCODING, | |
| "http.response.incomplete_chunked_encoding" }, | |
| { net::ERR_SSL_VERSION_OR_CIPHER_MISMATCH, | |
| "ssl.version_or_cipher_mismatch" }, | |
| { net::ERR_BAD_SSL_CLIENT_AUTH_CERT, "ssl.bad_client_auth_cert" }, | |
| { net::ERR_INVALID_CHUNKED_ENCODING, | |
| "http.response.invalid_chunked_encoding" }, | |
| { net::ERR_RESPONSE_HEADERS_TRUNCATED, "http.response.headers.truncated" }, | |
| { net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, | |
| "http.request.range_not_satisfiable" }, | |
| { net::ERR_INVALID_RESPONSE, "http.response.invalid" }, | |
| { net::ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION, | |
| "http.response.headers.multiple_content_disposition" }, | |
| { net::ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH, | |
| "http.response.headers.multiple_content_length" }, | |
| { net::ERR_SSL_UNRECOGNIZED_NAME_ALERT, "ssl.unrecognized_name_alert" } | |
| }; | |
| bool CanReportFullBeaconURLToCollector(const GURL& beacon_url, | |
| const GURL& collector_url) { | |
| return beacon_url.GetOrigin() == collector_url.GetOrigin(); | |
| } | |
| } // namespace | |
| // static | |
| bool GetDomainReliabilityBeaconStatus( | |
| int net_error, | |
| int http_response_code, | |
| std::string* beacon_status_out) { | |
| if (net_error == net::OK) { | |
| if (http_response_code >= 400 && http_response_code < 600) | |
| *beacon_status_out = "http.error"; | |
| else | |
| *beacon_status_out = "ok"; | |
| return true; | |
| } | |
| // TODO(juliatuttle): Consider sorting and using binary search? | |
| for (size_t i = 0; i < base::size(net_error_map); i++) { | |
| if (net_error_map[i].net_error == net_error) { | |
| *beacon_status_out = net_error_map[i].beacon_status; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| // TODO(juliatuttle): Consider using NPN/ALPN instead, if there's a good way to | |
| // differentiate HTTP and HTTPS. | |
| std::string GetDomainReliabilityProtocol( | |
| net::HttpResponseInfo::ConnectionInfo connection_info, | |
| bool ssl_info_populated) { | |
| switch (connection_info) { | |
| case net::HttpResponseInfo::CONNECTION_INFO_UNKNOWN: | |
| return ""; | |
| case net::HttpResponseInfo::CONNECTION_INFO_HTTP0_9: | |
| case net::HttpResponseInfo::CONNECTION_INFO_HTTP1_0: | |
| case net::HttpResponseInfo::CONNECTION_INFO_HTTP1_1: | |
| return ssl_info_populated ? "HTTPS" : "HTTP"; | |
| case net::HttpResponseInfo::CONNECTION_INFO_DEPRECATED_SPDY2: | |
| case net::HttpResponseInfo::CONNECTION_INFO_DEPRECATED_SPDY3: | |
| case net::HttpResponseInfo::CONNECTION_INFO_DEPRECATED_HTTP2_14: | |
| case net::HttpResponseInfo::CONNECTION_INFO_DEPRECATED_HTTP2_15: | |
| case net::HttpResponseInfo::CONNECTION_INFO_HTTP2: | |
| return "SPDY"; | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_UNKNOWN_VERSION: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_32: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_33: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_34: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_35: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_36: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_37: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_38: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_39: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_40: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_41: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_42: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_43: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_44: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_45: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_46: | |
| case net::HttpResponseInfo::CONNECTION_INFO_QUIC_99: | |
| return "QUIC"; | |
| case net::HttpResponseInfo::NUM_OF_CONNECTION_INFOS: | |
| NOTREACHED(); | |
| return ""; | |
| } | |
| NOTREACHED(); | |
| return ""; | |
| } | |
| int GetNetErrorFromURLRequestStatus(const net::URLRequestStatus& status) { | |
| switch (status.status()) { | |
| case net::URLRequestStatus::SUCCESS: | |
| return net::OK; | |
| case net::URLRequestStatus::CANCELED: | |
| return net::ERR_ABORTED; | |
| case net::URLRequestStatus::FAILED: | |
| return status.error(); | |
| default: | |
| NOTREACHED(); | |
| return net::ERR_FAILED; | |
| } | |
| } | |
| void GetUploadResultFromResponseDetails( | |
| int net_error, | |
| int http_response_code, | |
| base::TimeDelta retry_after, | |
| DomainReliabilityUploader::UploadResult* result) { | |
| if (net_error == net::OK && http_response_code == 200) { | |
| result->status = DomainReliabilityUploader::UploadResult::SUCCESS; | |
| return; | |
| } | |
| if (net_error == net::OK && | |
| http_response_code == 503 && | |
| !retry_after.is_zero()) { | |
| result->status = DomainReliabilityUploader::UploadResult::RETRY_AFTER; | |
| result->retry_after = retry_after; | |
| return; | |
| } | |
| result->status = DomainReliabilityUploader::UploadResult::FAILURE; | |
| return; | |
| } | |
| // N.B. This uses a std::vector<std::unique_ptr<>> because that's what | |
| // JSONValueConverter uses for repeated fields of any type, and Config uses | |
| // JSONValueConverter to parse JSON configs. | |
| GURL SanitizeURLForReport( | |
| const GURL& beacon_url, | |
| const GURL& collector_url, | |
| const std::vector<std::unique_ptr<std::string>>& path_prefixes) { | |
| if (CanReportFullBeaconURLToCollector(beacon_url, collector_url)) | |
| return beacon_url.GetAsReferrer(); | |
| std::string path = beacon_url.path(); | |
| const std::string empty_path; | |
| const std::string* longest_path_prefix = &empty_path; | |
| for (const auto& path_prefix : path_prefixes) { | |
| if (path.substr(0, path_prefix->length()) == *path_prefix && | |
| path_prefix->length() > longest_path_prefix->length()) { | |
| longest_path_prefix = path_prefix.get(); | |
| } | |
| } | |
| GURL::Replacements replacements; | |
| replacements.ClearUsername(); | |
| replacements.ClearPassword(); | |
| replacements.SetPathStr(*longest_path_prefix); | |
| replacements.ClearQuery(); | |
| replacements.ClearRef(); | |
| return beacon_url.ReplaceComponents(replacements); | |
| } | |
| namespace { | |
| class ActualTimer : public MockableTime::Timer { | |
| public: | |
| ActualTimer() {} | |
| ~ActualTimer() override {} | |
| // MockableTime::Timer implementation: | |
| void Start(const base::Location& posted_from, | |
| base::TimeDelta delay, | |
| const base::Closure& user_task) override { | |
| base_timer_.Start(posted_from, delay, user_task); | |
| } | |
| void Stop() override { base_timer_.Stop(); } | |
| bool IsRunning() override { return base_timer_.IsRunning(); } | |
| private: | |
| base::OneShotTimer base_timer_; | |
| }; | |
| } // namespace | |
| MockableTime::Timer::~Timer() {} | |
| MockableTime::Timer::Timer() {} | |
| MockableTime::~MockableTime() {} | |
| MockableTime::MockableTime() {} | |
| ActualTime::ActualTime() {} | |
| ActualTime::~ActualTime() {} | |
| base::Time ActualTime::Now() const { | |
| return base::Time::Now(); | |
| } | |
| base::TimeTicks ActualTime::NowTicks() const { | |
| return base::TimeTicks::Now(); | |
| } | |
| std::unique_ptr<MockableTime::Timer> ActualTime::CreateTimer() { | |
| return std::unique_ptr<MockableTime::Timer>(new ActualTimer()); | |
| } | |
| } // namespace domain_reliability |