1 // Copyright 2016 The Chromium Authors
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 "net/reporting/reporting_uploader.h"
6
7 #include <string>
8 #include <utility>
9 #include <vector>
10
11 #include "base/functional/callback_helpers.h"
12 #include "base/memory/raw_ptr.h"
13 #include "base/not_fatal_until.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "net/base/elements_upload_data_stream.h"
17 #include "net/base/isolation_info.h"
18 #include "net/base/load_flags.h"
19 #include "net/base/network_anonymization_key.h"
20 #include "net/base/upload_bytes_element_reader.h"
21 #include "net/http/http_response_headers.h"
22 #include "net/traffic_annotation/network_traffic_annotation.h"
23 #include "net/url_request/redirect_info.h"
24 #include "net/url_request/url_request_context.h"
25 #include "url/gurl.h"
26 #include "url/origin.h"
27
28 namespace net {
29
30 namespace {
31
32 constexpr char kUploadContentType[] = "application/reports+json";
33
34 constexpr net::NetworkTrafficAnnotationTag kReportUploadTrafficAnnotation =
35 net::DefineNetworkTrafficAnnotation("reporting", R"(
36 semantics {
37 sender: "Reporting API"
38 description:
39 "The Reporting API reports various issues back to website owners "
40 "to help them detect and fix problems."
41 trigger:
42 "Encountering issues. Examples of these issues are Content "
43 "Security Policy violations and Interventions/Deprecations "
44 "encountered. See draft of reporting spec here: "
45 "https://wicg.github.io/reporting."
46 data: "Details of the issue, depending on issue type."
47 destination: OTHER
48 }
49 policy {
50 cookies_allowed: NO
51 setting: "This feature cannot be disabled by settings."
52 policy_exception_justification: "Not implemented."
53 })");
54
55 // Returns true if |request| contains any of the |allowed_values| in a response
56 // header field named |header|. |allowed_values| are expected to be lower-case
57 // and the check is case-insensitive.
HasHeaderValues(URLRequest * request,const std::string & header,const std::set<std::string> & allowed_values)58 bool HasHeaderValues(URLRequest* request,
59 const std::string& header,
60 const std::set<std::string>& allowed_values) {
61 std::string response_headers = request->GetResponseHeaderByName(header);
62 const std::vector<std::string> response_values =
63 base::SplitString(base::ToLowerASCII(response_headers), ",",
64 base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
65 for (const auto& value : response_values) {
66 if (allowed_values.find(value) != allowed_values.end())
67 return true;
68 }
69 return false;
70 }
71
72 ReportingUploader::Outcome ResponseCodeToOutcome(int response_code) {
73 if (response_code >= 200 && response_code <= 299)
74 return ReportingUploader::Outcome::SUCCESS;
75 if (response_code == 410)
76 return ReportingUploader::Outcome::REMOVE_ENDPOINT;
77 return ReportingUploader::Outcome::FAILURE;
78 }
79
80 struct PendingUpload {
81 enum State { CREATED, SENDING_PREFLIGHT, SENDING_PAYLOAD };
82
83 PendingUpload(const url::Origin& report_origin,
84 const GURL& url,
85 const IsolationInfo& isolation_info,
86 const std::string& json,
87 int max_depth,
88 ReportingUploader::UploadCallback callback)
89 : report_origin(report_origin),
90 url(url),
91 isolation_info(isolation_info),
92 payload_reader(UploadOwnedBytesElementReader::CreateWithString(json)),
93 max_depth(max_depth),
94 callback(std::move(callback)) {}
95
96 void RunCallback(ReportingUploader::Outcome outcome) {
97 std::move(callback).Run(outcome);
98 }
99
100 State state = CREATED;
101 const url::Origin report_origin;
102 const GURL url;
103 const IsolationInfo isolation_info;
104 std::unique_ptr<UploadElementReader> payload_reader;
105 int max_depth;
106 ReportingUploader::UploadCallback callback;
107 std::unique_ptr<URLRequest> request;
108 };
109
110 class ReportingUploaderImpl : public ReportingUploader, URLRequest::Delegate {
111 public:
112 explicit ReportingUploaderImpl(const URLRequestContext* context)
113 : context_(context) {
114 DCHECK(context_);
115 }
116
117 ~ReportingUploaderImpl() override {
118 for (auto& request_and_upload : uploads_) {
119 auto& upload = request_and_upload.second;
120 upload->RunCallback(Outcome::FAILURE);
121 }
122 }
123
124 void StartUpload(const url::Origin& report_origin,
125 const GURL& url,
126 const IsolationInfo& isolation_info,
127 const std::string& json,
128 int max_depth,
129 bool eligible_for_credentials,
130 UploadCallback callback) override {
131 auto upload =
132 std::make_unique<PendingUpload>(report_origin, url, isolation_info,
133 json, max_depth, std::move(callback));
134 auto collector_origin = url::Origin::Create(url);
135 if (collector_origin == report_origin) {
136 // Skip the preflight check if the reports are being sent to the same
137 // origin as the requests they describe.
138 StartPayloadRequest(std::move(upload), eligible_for_credentials);
139 } else {
140 StartPreflightRequest(std::move(upload));
141 }
142 }
143
144 void OnShutdown() override {
145 // Cancels all pending uploads.
146 uploads_.clear();
147 }
148
149 void StartPreflightRequest(std::unique_ptr<PendingUpload> upload) {
150 DCHECK(upload->state == PendingUpload::CREATED);
151
152 upload->state = PendingUpload::SENDING_PREFLIGHT;
153 upload->request = context_->CreateRequest(upload->url, IDLE, this,
154 kReportUploadTrafficAnnotation);
155
156 upload->request->set_method("OPTIONS");
157
158 upload->request->SetLoadFlags(LOAD_DISABLE_CACHE);
159 upload->request->set_allow_credentials(false);
160 upload->request->set_isolation_info(upload->isolation_info);
161
162 upload->request->set_initiator(upload->report_origin);
163 upload->request->SetExtraRequestHeaderByName(
164 HttpRequestHeaders::kOrigin, upload->report_origin.Serialize(), true);
165 upload->request->SetExtraRequestHeaderByName(
166 "Access-Control-Request-Method", "POST", true);
167 upload->request->SetExtraRequestHeaderByName(
168 "Access-Control-Request-Headers", "content-type", true);
169
170 // Set the max_depth for this request, to cap how deep a stack of "reports
171 // about reports" can get. (Without this, a Reporting policy that uploads
172 // reports to the same origin can cause an infinite stack of reports about
173 // reports.)
174 upload->request->set_reporting_upload_depth(upload->max_depth + 1);
175
176 URLRequest* raw_request = upload->request.get();
177 uploads_[raw_request] = std::move(upload);
178 raw_request->Start();
179 }
180
StartPayloadRequest(std::unique_ptr<PendingUpload> upload,bool eligible_for_credentials)181 void StartPayloadRequest(std::unique_ptr<PendingUpload> upload,
182 bool eligible_for_credentials) {
183 DCHECK(upload->state == PendingUpload::CREATED ||
184 upload->state == PendingUpload::SENDING_PREFLIGHT);
185
186 upload->state = PendingUpload::SENDING_PAYLOAD;
187 upload->request = context_->CreateRequest(upload->url, IDLE, this,
188 kReportUploadTrafficAnnotation);
189 upload->request->set_method("POST");
190
191 upload->request->SetLoadFlags(LOAD_DISABLE_CACHE);
192
193 // Credentials are sent for V1 reports, if the endpoint is same-origin with
194 // the site generating the report (this will be set to false either by the
195 // delivery agent determining that this is a V0 report, or by `StartUpload`
196 // determining that this is a cross-origin case, and taking the CORS
197 // preflight path).
198 upload->request->set_allow_credentials(eligible_for_credentials);
199 // The site for cookies is taken from the reporting source's IsolationInfo,
200 // in the case of V1 reporting endpoints, and will be null for V0 reports.
201 upload->request->set_site_for_cookies(
202 upload->isolation_info.site_for_cookies());
203
204 // `upload->report_origin` corresponds to the origin of the URL with the
205 // response headers that caused the report to sent, so use this for the
206 // report request initiator as well. This also aligns with how we use the
207 // report origin in the 'Origin:' header for the preflight we send if the
208 // collector origin is cross-origin with the report origin.
209 upload->request->set_initiator(upload->report_origin);
210
211 // `upload->isolation_info` usually corresponds to the context where a
212 // report was generated. For example, if a document loads a resource with a
213 // NEL policy, the IsolationInfo will correspond to that document (whereas
214 // `upload->report_origin` will correspond to the resource URL). Use this
215 // same IsolationInfo for the report upload URLRequest.
216 //
217 // Note that the values within `upload->isolation_info` can vary widely
218 // based on a number of factors:
219 // - For reports corresponding to enterprise endpoints, the IsolationInfo
220 // will be transient (see
221 // `ReportingCacheImpl::GetIsolationInfoForEndpoint()`).
222 //
223 // - For V0 reports when Network State Partitioning (NSP) is disabled, the
224 // IsolationInfo will be empty since it is created from an empty NAK (See
225 // `ReportingServiceImpl::FixupNetworkAnonymizationKey()`,
226 // `ReportingCacheImpl::GetIsolationInfoForEndpoint()`, and
227 // `IsolationInfo::DoNotUseCreatePartialFromNak()`). This is CHECK'd
228 // below.
229 //
230 // - For V0 reports from cross-site contexts (when NSP is enabled), the
231 // IsolationInfo will be generated from a NetworkAnonymizationKey and the
232 // frame origin will be opaque.
233 //
234 // - For V0 reports from same-site contexts (when NSP is enabled), the
235 // frame origin will be created from the top-level site, losing full host
236 // and port information.
237 if (upload->isolation_info.IsEmpty()) {
238 CHECK(!NetworkAnonymizationKey::IsPartitioningEnabled());
239 }
240 upload->request->set_isolation_info(upload->isolation_info);
241
242 upload->request->SetExtraRequestHeaderByName(
243 HttpRequestHeaders::kContentType, kUploadContentType, true);
244
245 upload->request->set_upload(ElementsUploadDataStream::CreateWithReader(
246 std::move(upload->payload_reader)));
247
248 // Set the max_depth for this request, to cap how deep a stack of "reports
249 // about reports" can get. (Without this, a Reporting policy that uploads
250 // reports to the same origin can cause an infinite stack of reports about
251 // reports.)
252 upload->request->set_reporting_upload_depth(upload->max_depth + 1);
253
254 URLRequest* raw_request = upload->request.get();
255 uploads_[raw_request] = std::move(upload);
256 raw_request->Start();
257 }
258
259 // URLRequest::Delegate implementation:
260
OnReceivedRedirect(URLRequest * request,const RedirectInfo & redirect_info,bool * defer_redirect)261 void OnReceivedRedirect(URLRequest* request,
262 const RedirectInfo& redirect_info,
263 bool* defer_redirect) override {
264 if (!redirect_info.new_url.SchemeIsCryptographic()) {
265 request->Cancel();
266 return;
267 }
268 }
269
OnAuthRequired(URLRequest * request,const AuthChallengeInfo & auth_info)270 void OnAuthRequired(URLRequest* request,
271 const AuthChallengeInfo& auth_info) override {
272 request->Cancel();
273 }
274
OnCertificateRequested(URLRequest * request,SSLCertRequestInfo * cert_request_info)275 void OnCertificateRequested(URLRequest* request,
276 SSLCertRequestInfo* cert_request_info) override {
277 request->Cancel();
278 }
279
OnSSLCertificateError(URLRequest * request,int net_error,const SSLInfo & ssl_info,bool fatal)280 void OnSSLCertificateError(URLRequest* request,
281 int net_error,
282 const SSLInfo& ssl_info,
283 bool fatal) override {
284 request->Cancel();
285 }
286
OnResponseStarted(URLRequest * request,int net_error)287 void OnResponseStarted(URLRequest* request, int net_error) override {
288 // Grab Upload from map, and hold on to it in a local unique_ptr so it's
289 // removed at the end of the method.
290 auto it = uploads_.find(request);
291 CHECK(it != uploads_.end(), base::NotFatalUntil::M130);
292 std::unique_ptr<PendingUpload> upload = std::move(it->second);
293 uploads_.erase(it);
294
295 if (net_error != OK) {
296 upload->RunCallback(ReportingUploader::Outcome::FAILURE);
297 return;
298 }
299
300 // request->GetResponseCode() should work, but doesn't in the cases above
301 // where the request was canceled, so get the response code by hand.
302 // TODO(juliatuttle): Check if mmenke fixed this yet.
303 HttpResponseHeaders* headers = request->response_headers();
304 int response_code = headers ? headers->response_code() : 0;
305
306 switch (upload->state) {
307 case PendingUpload::SENDING_PREFLIGHT:
308 HandlePreflightResponse(std::move(upload), response_code);
309 break;
310 case PendingUpload::SENDING_PAYLOAD:
311 HandlePayloadResponse(std::move(upload), response_code);
312 break;
313 default:
314 NOTREACHED();
315 }
316 }
317
HandlePreflightResponse(std::unique_ptr<PendingUpload> upload,int response_code)318 void HandlePreflightResponse(std::unique_ptr<PendingUpload> upload,
319 int response_code) {
320 // Check that the preflight succeeded: it must have an HTTP OK status code,
321 // with the following headers:
322 // - Access-Control-Allow-Origin: * or the report origin
323 // - Access-Control-Allow-Headers: * or Content-Type
324 // Note that * is allowed here as the credentials mode is never 'include'.
325 // Access-Control-Allow-Methods is not checked, as the preflight is always
326 // for a POST method, which is safelisted.
327 URLRequest* request = upload->request.get();
328 bool preflight_succeeded =
329 (response_code >= 200 && response_code <= 299) &&
330 HasHeaderValues(
331 request, "Access-Control-Allow-Origin",
332 {"*", base::ToLowerASCII(upload->report_origin.Serialize())}) &&
333 HasHeaderValues(request, "Access-Control-Allow-Headers",
334 {"*", "content-type"});
335 if (!preflight_succeeded) {
336 upload->RunCallback(ReportingUploader::Outcome::FAILURE);
337 return;
338 }
339 // Any upload which required CORS should not receive credentials, as they
340 // are sent to same-origin endpoints only.
341 StartPayloadRequest(std::move(upload), /*eligible_for_credentials=*/false);
342 }
343
HandlePayloadResponse(std::unique_ptr<PendingUpload> upload,int response_code)344 void HandlePayloadResponse(std::unique_ptr<PendingUpload> upload,
345 int response_code) {
346 upload->RunCallback(ResponseCodeToOutcome(response_code));
347 }
348
OnReadCompleted(URLRequest * request,int bytes_read)349 void OnReadCompleted(URLRequest* request, int bytes_read) override {
350 // Reporting doesn't need anything in the body of the response, so it
351 // doesn't read it, so it should never get OnReadCompleted calls.
352 NOTREACHED();
353 }
354
GetPendingUploadCountForTesting() const355 int GetPendingUploadCountForTesting() const override {
356 return uploads_.size();
357 }
358
359 private:
360 raw_ptr<const URLRequestContext> context_;
361 std::map<const URLRequest*, std::unique_ptr<PendingUpload>> uploads_;
362 };
363
364 } // namespace
365
366 ReportingUploader::~ReportingUploader() = default;
367
368 // static
Create(const URLRequestContext * context)369 std::unique_ptr<ReportingUploader> ReportingUploader::Create(
370 const URLRequestContext* context) {
371 return std::make_unique<ReportingUploaderImpl>(context);
372 }
373
374 } // namespace net
375