| // 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 |