1 // Copyright 2014 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 "components/domain_reliability/util.h"
6
7 #include <stddef.h>
8
9 #include "base/callback.h"
10 #include "base/logging.h"
11 #include "base/memory/weak_ptr.h"
12 #include "base/stl_util.h"
13 #include "base/time/time.h"
14 #include "base/timer/timer.h"
15 #include "net/base/net_errors.h"
16
17 namespace domain_reliability {
18
19 namespace {
20
21 const struct NetErrorMapping {
22 int net_error;
23 const char* beacon_status;
24 } net_error_map[] = {
25 {net::OK, "ok"},
26 {net::ERR_ABORTED, "aborted"},
27 {net::ERR_TIMED_OUT, "tcp.connection.timed_out"},
28 {net::ERR_CONNECTION_CLOSED, "tcp.connection.closed"},
29 {net::ERR_CONNECTION_RESET, "tcp.connection.reset"},
30 {net::ERR_CONNECTION_REFUSED, "tcp.connection.refused"},
31 {net::ERR_CONNECTION_ABORTED, "tcp.connection.aborted"},
32 {net::ERR_CONNECTION_FAILED, "tcp.connection.failed"},
33 {net::ERR_NAME_NOT_RESOLVED, "dns"},
34 {net::ERR_SSL_PROTOCOL_ERROR, "ssl.protocol.error"},
35 {net::ERR_ADDRESS_INVALID, "tcp.connection.address_invalid"},
36 {net::ERR_ADDRESS_UNREACHABLE, "tcp.connection.address_unreachable"},
37 {net::ERR_CONNECTION_TIMED_OUT, "tcp.connection.timed_out"},
38 {net::ERR_NAME_RESOLUTION_FAILED, "dns"},
39 {net::ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN,
40 "ssl.cert.pinned_key_not_in_cert_chain"},
41 {net::ERR_CERT_COMMON_NAME_INVALID, "ssl.cert.name_invalid"},
42 {net::ERR_CERT_DATE_INVALID, "ssl.cert.date_invalid"},
43 {net::ERR_CERT_AUTHORITY_INVALID, "ssl.cert.authority_invalid"},
44 {net::ERR_CERT_KNOWN_INTERCEPTION_BLOCKED, "ssl.cert.authority_invalid"},
45 {net::ERR_CERT_REVOKED, "ssl.cert.revoked"},
46 {net::ERR_CERT_INVALID, "ssl.cert.invalid"},
47 {net::ERR_EMPTY_RESPONSE, "http.response.empty"},
48 {net::ERR_HTTP2_PING_FAILED, "spdy.ping_failed"},
49 {net::ERR_HTTP2_PROTOCOL_ERROR, "spdy.protocol"},
50 {net::ERR_QUIC_PROTOCOL_ERROR, "quic.protocol"},
51 {net::ERR_DNS_MALFORMED_RESPONSE, "dns.protocol"},
52 {net::ERR_DNS_SERVER_FAILED, "dns.server"},
53 {net::ERR_DNS_TIMED_OUT, "dns.timed_out"},
54 {net::ERR_INSECURE_RESPONSE, "ssl"},
55 {net::ERR_CONTENT_LENGTH_MISMATCH, "http.response.content_length_mismatch"},
56 {net::ERR_INCOMPLETE_CHUNKED_ENCODING,
57 "http.response.incomplete_chunked_encoding"},
58 {net::ERR_SSL_VERSION_OR_CIPHER_MISMATCH, "ssl.version_or_cipher_mismatch"},
59 {net::ERR_BAD_SSL_CLIENT_AUTH_CERT, "ssl.bad_client_auth_cert"},
60 {net::ERR_INVALID_CHUNKED_ENCODING,
61 "http.response.invalid_chunked_encoding"},
62 {net::ERR_RESPONSE_HEADERS_TRUNCATED, "http.response.headers.truncated"},
63 {net::ERR_REQUEST_RANGE_NOT_SATISFIABLE,
64 "http.request.range_not_satisfiable"},
65 {net::ERR_INVALID_RESPONSE, "http.response.invalid"},
66 {net::ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION,
67 "http.response.headers.multiple_content_disposition"},
68 {net::ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH,
69 "http.response.headers.multiple_content_length"},
70 {net::ERR_SSL_UNRECOGNIZED_NAME_ALERT, "ssl.unrecognized_name_alert"}};
71
CanReportFullBeaconURLToCollector(const GURL & beacon_url,const GURL & collector_url)72 bool CanReportFullBeaconURLToCollector(const GURL& beacon_url,
73 const GURL& collector_url) {
74 return beacon_url.GetOrigin() == collector_url.GetOrigin();
75 }
76
77 } // namespace
78
79 // static
GetDomainReliabilityBeaconStatus(int net_error,int http_response_code,std::string * beacon_status_out)80 bool GetDomainReliabilityBeaconStatus(
81 int net_error,
82 int http_response_code,
83 std::string* beacon_status_out) {
84 if (net_error == net::OK) {
85 if (http_response_code >= 400 && http_response_code < 600)
86 *beacon_status_out = "http.error";
87 else
88 *beacon_status_out = "ok";
89 return true;
90 }
91
92 // TODO(juliatuttle): Consider sorting and using binary search?
93 for (size_t i = 0; i < base::size(net_error_map); i++) {
94 if (net_error_map[i].net_error == net_error) {
95 *beacon_status_out = net_error_map[i].beacon_status;
96 return true;
97 }
98 }
99 return false;
100 }
101
102 // TODO(juliatuttle): Consider using NPN/ALPN instead, if there's a good way to
103 // differentiate HTTP and HTTPS.
GetDomainReliabilityProtocol(net::HttpResponseInfo::ConnectionInfo connection_info,bool ssl_info_populated)104 std::string GetDomainReliabilityProtocol(
105 net::HttpResponseInfo::ConnectionInfo connection_info,
106 bool ssl_info_populated) {
107 switch (net::HttpResponseInfo::ConnectionInfoToCoarse(connection_info)) {
108 case net::HttpResponseInfo::CONNECTION_INFO_COARSE_HTTP1:
109 return ssl_info_populated ? "HTTPS" : "HTTP";
110 case net::HttpResponseInfo::CONNECTION_INFO_COARSE_HTTP2:
111 return "SPDY";
112 case net::HttpResponseInfo::CONNECTION_INFO_COARSE_QUIC:
113 return "QUIC";
114 case net::HttpResponseInfo::CONNECTION_INFO_COARSE_OTHER:
115 return "";
116 }
117 NOTREACHED();
118 return "";
119 }
120
GetNetErrorFromURLRequestStatus(const net::URLRequestStatus & status)121 int GetNetErrorFromURLRequestStatus(const net::URLRequestStatus& status) {
122 switch (status.status()) {
123 case net::URLRequestStatus::SUCCESS:
124 return net::OK;
125 case net::URLRequestStatus::CANCELED:
126 return net::ERR_ABORTED;
127 case net::URLRequestStatus::FAILED:
128 return status.error();
129 default:
130 NOTREACHED();
131 return net::ERR_FAILED;
132 }
133 }
134
GetUploadResultFromResponseDetails(int net_error,int http_response_code,base::TimeDelta retry_after,DomainReliabilityUploader::UploadResult * result)135 void GetUploadResultFromResponseDetails(
136 int net_error,
137 int http_response_code,
138 base::TimeDelta retry_after,
139 DomainReliabilityUploader::UploadResult* result) {
140 if (net_error == net::OK && http_response_code == 200) {
141 result->status = DomainReliabilityUploader::UploadResult::SUCCESS;
142 return;
143 }
144
145 if (net_error == net::OK &&
146 http_response_code == 503 &&
147 !retry_after.is_zero()) {
148 result->status = DomainReliabilityUploader::UploadResult::RETRY_AFTER;
149 result->retry_after = retry_after;
150 return;
151 }
152
153 result->status = DomainReliabilityUploader::UploadResult::FAILURE;
154 return;
155 }
156
157 // N.B. This uses a std::vector<std::unique_ptr<>> because that's what
158 // JSONValueConverter uses for repeated fields of any type, and Config uses
159 // JSONValueConverter to parse JSON configs.
SanitizeURLForReport(const GURL & beacon_url,const GURL & collector_url,const std::vector<std::unique_ptr<std::string>> & path_prefixes)160 GURL SanitizeURLForReport(
161 const GURL& beacon_url,
162 const GURL& collector_url,
163 const std::vector<std::unique_ptr<std::string>>& path_prefixes) {
164 if (CanReportFullBeaconURLToCollector(beacon_url, collector_url))
165 return beacon_url.GetAsReferrer();
166
167 std::string path = beacon_url.path();
168 const std::string empty_path;
169 const std::string* longest_path_prefix = &empty_path;
170 for (const auto& path_prefix : path_prefixes) {
171 if (path.substr(0, path_prefix->length()) == *path_prefix &&
172 path_prefix->length() > longest_path_prefix->length()) {
173 longest_path_prefix = path_prefix.get();
174 }
175 }
176
177 GURL::Replacements replacements;
178 replacements.ClearUsername();
179 replacements.ClearPassword();
180 replacements.SetPathStr(*longest_path_prefix);
181 replacements.ClearQuery();
182 replacements.ClearRef();
183 return beacon_url.ReplaceComponents(replacements);
184 }
185
186 namespace {
187
188 class ActualTimer : public MockableTime::Timer {
189 public:
ActualTimer()190 ActualTimer() {}
~ActualTimer()191 ~ActualTimer() override {}
192
193 // MockableTime::Timer implementation:
Start(const base::Location & posted_from,base::TimeDelta delay,base::OnceClosure user_task)194 void Start(const base::Location& posted_from,
195 base::TimeDelta delay,
196 base::OnceClosure user_task) override {
197 base_timer_.Start(posted_from, delay, std::move(user_task));
198 }
199
Stop()200 void Stop() override { base_timer_.Stop(); }
201
IsRunning()202 bool IsRunning() override { return base_timer_.IsRunning(); }
203
204 private:
205 base::OneShotTimer base_timer_;
206 };
207
208 } // namespace
209
~Timer()210 MockableTime::Timer::~Timer() {}
Timer()211 MockableTime::Timer::Timer() {}
212
~MockableTime()213 MockableTime::~MockableTime() {}
MockableTime()214 MockableTime::MockableTime() {}
215
ActualTime()216 ActualTime::ActualTime() {}
~ActualTime()217 ActualTime::~ActualTime() {}
218
Now() const219 base::Time ActualTime::Now() const {
220 return base::Time::Now();
221 }
NowTicks() const222 base::TimeTicks ActualTime::NowTicks() const {
223 return base::TimeTicks::Now();
224 }
225
CreateTimer()226 std::unique_ptr<MockableTime::Timer> ActualTime::CreateTimer() {
227 return std::unique_ptr<MockableTime::Timer>(new ActualTimer());
228 }
229
230 } // namespace domain_reliability
231