xref: /aosp_15_r20/external/cronet/net/reporting/reporting_uploader.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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/strings/string_split.h"
14 #include "base/strings/string_util.h"
15 #include "net/base/elements_upload_data_stream.h"
16 #include "net/base/isolation_info.h"
17 #include "net/base/load_flags.h"
18 #include "net/base/network_anonymization_key.h"
19 #include "net/base/upload_bytes_element_reader.h"
20 #include "net/http/http_response_headers.h"
21 #include "net/traffic_annotation/network_traffic_annotation.h"
22 #include "net/url_request/redirect_info.h"
23 #include "net/url_request/url_request_context.h"
24 #include "url/gurl.h"
25 #include "url/origin.h"
26 
27 namespace net {
28 
29 namespace {
30 
31 constexpr char kUploadContentType[] = "application/reports+json";
32 
33 constexpr net::NetworkTrafficAnnotationTag kReportUploadTrafficAnnotation =
34     net::DefineNetworkTrafficAnnotation("reporting", R"(
35         semantics {
36           sender: "Reporting API"
37           description:
38             "The Reporting API reports various issues back to website owners "
39             "to help them detect and fix problems."
40           trigger:
41             "Encountering issues. Examples of these issues are Content "
42             "Security Policy violations and Interventions/Deprecations "
43             "encountered. See draft of reporting spec here: "
44             "https://wicg.github.io/reporting."
45           data: "Details of the issue, depending on issue type."
46           destination: OTHER
47         }
48         policy {
49           cookies_allowed: NO
50           setting: "This feature cannot be disabled by settings."
51           policy_exception_justification: "Not implemented."
52         })");
53 
54 // Returns true if |request| contains any of the |allowed_values| in a response
55 // header field named |header|. |allowed_values| are expected to be lower-case
56 // and the check is case-insensitive.
HasHeaderValues(URLRequest * request,const std::string & header,const std::set<std::string> & allowed_values)57 bool HasHeaderValues(URLRequest* request,
58                      const std::string& header,
59                      const std::set<std::string>& allowed_values) {
60   std::string response_headers;
61   request->GetResponseHeaderByName(header, &response_headers);
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->SetExtraRequestHeaderByName(
163         HttpRequestHeaders::kOrigin, upload->report_origin.Serialize(), true);
164     upload->request->SetExtraRequestHeaderByName(
165         "Access-Control-Request-Method", "POST", true);
166     upload->request->SetExtraRequestHeaderByName(
167         "Access-Control-Request-Headers", "content-type", true);
168 
169     // Set the max_depth for this request, to cap how deep a stack of "reports
170     // about reports" can get.  (Without this, a Reporting policy that uploads
171     // reports to the same origin can cause an infinite stack of reports about
172     // reports.)
173     upload->request->set_reporting_upload_depth(upload->max_depth + 1);
174 
175     URLRequest* raw_request = upload->request.get();
176     uploads_[raw_request] = std::move(upload);
177     raw_request->Start();
178   }
179 
StartPayloadRequest(std::unique_ptr<PendingUpload> upload,bool eligible_for_credentials)180   void StartPayloadRequest(std::unique_ptr<PendingUpload> upload,
181                            bool eligible_for_credentials) {
182     DCHECK(upload->state == PendingUpload::CREATED ||
183            upload->state == PendingUpload::SENDING_PREFLIGHT);
184 
185     upload->state = PendingUpload::SENDING_PAYLOAD;
186     upload->request = context_->CreateRequest(upload->url, IDLE, this,
187                                               kReportUploadTrafficAnnotation);
188     upload->request->set_method("POST");
189 
190     upload->request->SetLoadFlags(LOAD_DISABLE_CACHE);
191 
192     // Credentials are sent for V1 reports, if the endpoint is same-origin with
193     // the site generating the report (this will be set to false either by the
194     // delivery agent determining that this is a V0 report, or by `StartUpload`
195     // determining that this is a cross-origin case, and taking the CORS
196     // preflight path).
197     upload->request->set_allow_credentials(eligible_for_credentials);
198     // The site for cookies is taken from the reporting source's IsolationInfo,
199     // in the case of V1 reporting endpoints, and will be null for V0 reports.
200     upload->request->set_site_for_cookies(
201         upload->isolation_info.site_for_cookies());
202     // Prior to using `isolation_info` directly here we built the
203     // `upload->network_anonymization_key` to create the set the
204     // `isolation_info`. As experiments roll out to determine whether network
205     // partitions should be double or triple keyed the isolation_info might have
206     // a null value for `frame_origin`. Thus we should again get it from
207     // `network_anonymization_key` until we can trust
208     // `isolation_info::frame_origin`.
209     upload->request->set_initiator(upload->report_origin);
210     upload->request->set_isolation_info(upload->isolation_info);
211 
212     upload->request->SetExtraRequestHeaderByName(
213         HttpRequestHeaders::kContentType, kUploadContentType, true);
214 
215     upload->request->set_upload(ElementsUploadDataStream::CreateWithReader(
216         std::move(upload->payload_reader), 0));
217 
218     // Set the max_depth for this request, to cap how deep a stack of "reports
219     // about reports" can get.  (Without this, a Reporting policy that uploads
220     // reports to the same origin can cause an infinite stack of reports about
221     // reports.)
222     upload->request->set_reporting_upload_depth(upload->max_depth + 1);
223 
224     URLRequest* raw_request = upload->request.get();
225     uploads_[raw_request] = std::move(upload);
226     raw_request->Start();
227   }
228 
229   // URLRequest::Delegate implementation:
230 
OnReceivedRedirect(URLRequest * request,const RedirectInfo & redirect_info,bool * defer_redirect)231   void OnReceivedRedirect(URLRequest* request,
232                           const RedirectInfo& redirect_info,
233                           bool* defer_redirect) override {
234     if (!redirect_info.new_url.SchemeIsCryptographic()) {
235       request->Cancel();
236       return;
237     }
238   }
239 
OnAuthRequired(URLRequest * request,const AuthChallengeInfo & auth_info)240   void OnAuthRequired(URLRequest* request,
241                       const AuthChallengeInfo& auth_info) override {
242     request->Cancel();
243   }
244 
OnCertificateRequested(URLRequest * request,SSLCertRequestInfo * cert_request_info)245   void OnCertificateRequested(URLRequest* request,
246                               SSLCertRequestInfo* cert_request_info) override {
247     request->Cancel();
248   }
249 
OnSSLCertificateError(URLRequest * request,int net_error,const SSLInfo & ssl_info,bool fatal)250   void OnSSLCertificateError(URLRequest* request,
251                              int net_error,
252                              const SSLInfo& ssl_info,
253                              bool fatal) override {
254     request->Cancel();
255   }
256 
OnResponseStarted(URLRequest * request,int net_error)257   void OnResponseStarted(URLRequest* request, int net_error) override {
258     // Grab Upload from map, and hold on to it in a local unique_ptr so it's
259     // removed at the end of the method.
260     auto it = uploads_.find(request);
261     DCHECK(it != uploads_.end());
262     std::unique_ptr<PendingUpload> upload = std::move(it->second);
263     uploads_.erase(it);
264 
265     if (net_error != OK) {
266       upload->RunCallback(ReportingUploader::Outcome::FAILURE);
267       return;
268     }
269 
270     // request->GetResponseCode() should work, but doesn't in the cases above
271     // where the request was canceled, so get the response code by hand.
272     // TODO(juliatuttle): Check if mmenke fixed this yet.
273     HttpResponseHeaders* headers = request->response_headers();
274     int response_code = headers ? headers->response_code() : 0;
275 
276     switch (upload->state) {
277       case PendingUpload::SENDING_PREFLIGHT:
278         HandlePreflightResponse(std::move(upload), response_code);
279         break;
280       case PendingUpload::SENDING_PAYLOAD:
281         HandlePayloadResponse(std::move(upload), response_code);
282         break;
283       default:
284         NOTREACHED();
285     }
286   }
287 
HandlePreflightResponse(std::unique_ptr<PendingUpload> upload,int response_code)288   void HandlePreflightResponse(std::unique_ptr<PendingUpload> upload,
289                                int response_code) {
290     // Check that the preflight succeeded: it must have an HTTP OK status code,
291     // with the following headers:
292     // - Access-Control-Allow-Origin: * or the report origin
293     // - Access-Control-Allow-Headers: * or Content-Type
294     // Note that * is allowed here as the credentials mode is never 'include'.
295     // Access-Control-Allow-Methods is not checked, as the preflight is always
296     // for a POST method, which is safelisted.
297     URLRequest* request = upload->request.get();
298     bool preflight_succeeded =
299         (response_code >= 200 && response_code <= 299) &&
300         HasHeaderValues(
301             request, "Access-Control-Allow-Origin",
302             {"*", base::ToLowerASCII(upload->report_origin.Serialize())}) &&
303         HasHeaderValues(request, "Access-Control-Allow-Headers",
304                         {"*", "content-type"});
305     if (!preflight_succeeded) {
306       upload->RunCallback(ReportingUploader::Outcome::FAILURE);
307       return;
308     }
309     // Any upload which required CORS should not receive credentials, as they
310     // are sent to same-origin endpoints only.
311     StartPayloadRequest(std::move(upload), /*eligible_for_credentials=*/false);
312   }
313 
HandlePayloadResponse(std::unique_ptr<PendingUpload> upload,int response_code)314   void HandlePayloadResponse(std::unique_ptr<PendingUpload> upload,
315                              int response_code) {
316     upload->RunCallback(ResponseCodeToOutcome(response_code));
317   }
318 
OnReadCompleted(URLRequest * request,int bytes_read)319   void OnReadCompleted(URLRequest* request, int bytes_read) override {
320     // Reporting doesn't need anything in the body of the response, so it
321     // doesn't read it, so it should never get OnReadCompleted calls.
322     NOTREACHED();
323   }
324 
GetPendingUploadCountForTesting() const325   int GetPendingUploadCountForTesting() const override {
326     return uploads_.size();
327   }
328 
329  private:
330   raw_ptr<const URLRequestContext> context_;
331   std::map<const URLRequest*, std::unique_ptr<PendingUpload>> uploads_;
332 };
333 
334 }  // namespace
335 
336 ReportingUploader::~ReportingUploader() = default;
337 
338 // static
Create(const URLRequestContext * context)339 std::unique_ptr<ReportingUploader> ReportingUploader::Create(
340     const URLRequestContext* context) {
341   return std::make_unique<ReportingUploaderImpl>(context);
342 }
343 
344 }  // namespace net
345