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