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/memory/weak_ptr.h"
11 #include "base/notreached.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 
GetUploadResultFromResponseDetails(int net_error,int http_response_code,base::TimeDelta retry_after)121 DomainReliabilityUploader::UploadResult GetUploadResultFromResponseDetails(
122     int net_error,
123     int http_response_code,
124     base::TimeDelta retry_after) {
125   DomainReliabilityUploader::UploadResult result;
126   if (net_error == net::OK && http_response_code == 200) {
127     result.status = DomainReliabilityUploader::UploadResult::SUCCESS;
128     return result;
129   }
130 
131   if (net_error == net::OK && http_response_code == 503 &&
132       !retry_after.is_zero()) {
133     result.status = DomainReliabilityUploader::UploadResult::RETRY_AFTER;
134     result.retry_after = retry_after;
135     return result;
136   }
137 
138   result.status = DomainReliabilityUploader::UploadResult::FAILURE;
139   return result;
140 }
141 
142 // N.B. This uses a std::vector<std::unique_ptr<>> because that's what
143 // JSONValueConverter uses for repeated fields of any type, and Config uses
144 // JSONValueConverter to parse JSON configs.
SanitizeURLForReport(const GURL & beacon_url,const GURL & collector_url,const std::vector<std::unique_ptr<std::string>> & path_prefixes)145 GURL SanitizeURLForReport(
146     const GURL& beacon_url,
147     const GURL& collector_url,
148     const std::vector<std::unique_ptr<std::string>>& path_prefixes) {
149   if (CanReportFullBeaconURLToCollector(beacon_url, collector_url))
150     return beacon_url.GetAsReferrer();
151 
152   std::string path = beacon_url.path();
153   const std::string empty_path;
154   const std::string* longest_path_prefix = &empty_path;
155   for (const auto& path_prefix : path_prefixes) {
156     if (path.substr(0, path_prefix->length()) == *path_prefix &&
157         path_prefix->length() > longest_path_prefix->length()) {
158       longest_path_prefix = path_prefix.get();
159     }
160   }
161 
162   GURL::Replacements replacements;
163   replacements.ClearUsername();
164   replacements.ClearPassword();
165   replacements.SetPathStr(*longest_path_prefix);
166   replacements.ClearQuery();
167   replacements.ClearRef();
168   return beacon_url.ReplaceComponents(replacements);
169 }
170 
171 namespace {
172 
173 class ActualTimer : public MockableTime::Timer {
174  public:
ActualTimer()175   ActualTimer() {}
~ActualTimer()176   ~ActualTimer() override {}
177 
178   // MockableTime::Timer implementation:
Start(const base::Location & posted_from,base::TimeDelta delay,base::OnceClosure user_task)179   void Start(const base::Location& posted_from,
180              base::TimeDelta delay,
181              base::OnceClosure user_task) override {
182     base_timer_.Start(posted_from, delay, std::move(user_task));
183   }
184 
Stop()185   void Stop() override { base_timer_.Stop(); }
186 
IsRunning()187   bool IsRunning() override { return base_timer_.IsRunning(); }
188 
189  private:
190   base::OneShotTimer base_timer_;
191 };
192 
193 }  // namespace
194 
~Timer()195 MockableTime::Timer::~Timer() {}
Timer()196 MockableTime::Timer::Timer() {}
197 
~MockableTime()198 MockableTime::~MockableTime() {}
MockableTime()199 MockableTime::MockableTime() {}
200 
ActualTime()201 ActualTime::ActualTime() {}
~ActualTime()202 ActualTime::~ActualTime() {}
203 
Now() const204 base::Time ActualTime::Now() const {
205   return base::Time::Now();
206 }
NowTicks() const207 base::TimeTicks ActualTime::NowTicks() const {
208   return base::TimeTicks::Now();
209 }
210 
CreateTimer()211 std::unique_ptr<MockableTime::Timer> ActualTime::CreateTimer() {
212   return std::unique_ptr<MockableTime::Timer>(new ActualTimer());
213 }
214 
215 }  // namespace domain_reliability
216