• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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