1 // Copyright 2020 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 "third_party/blink/renderer/platform/loader/fetch/url_loader/request_conversion.h"
6 
7 #include "media/media_buildflags.h"
8 #include "mojo/public/cpp/bindings/receiver.h"
9 #include "mojo/public/cpp/bindings/remote.h"
10 #include "mojo/public/cpp/system/data_pipe.h"
11 #include "net/base/request_priority.h"
12 #include "net/http/http_request_headers.h"
13 #include "net/http/http_util.h"
14 #include "services/network/public/cpp/constants.h"
15 #include "services/network/public/cpp/optional_trust_token_params.h"
16 #include "services/network/public/cpp/resource_request.h"
17 #include "services/network/public/cpp/resource_request_body.h"
18 #include "services/network/public/mojom/data_pipe_getter.mojom-blink.h"
19 #include "services/network/public/mojom/data_pipe_getter.mojom.h"
20 #include "services/network/public/mojom/trust_tokens.mojom-blink.h"
21 #include "services/network/public/mojom/trust_tokens.mojom.h"
22 #include "third_party/blink/public/common/features.h"
23 #include "third_party/blink/public/mojom/blob/blob.mojom.h"
24 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
25 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
26 #include "third_party/blink/public/platform/file_path_conversion.h"
27 #include "third_party/blink/public/platform/url_conversion.h"
28 #include "third_party/blink/public/platform/web_string.h"
29 #include "third_party/blink/renderer/platform/blob/blob_data.h"
30 #include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h"
31 #include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
32 #include "third_party/blink/renderer/platform/loader/fetch/trust_token_params_conversion.h"
33 #include "third_party/blink/renderer/platform/network/encoded_form_data.h"
34 #include "third_party/blink/renderer/platform/network/wrapped_data_pipe_getter.h"
35 
36 namespace blink {
37 
ImageAcceptHeader()38 const char* ImageAcceptHeader() {
39   static constexpr char kImageAcceptHeaderWithAvif[] =
40       "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8";
41   static constexpr size_t kOffset = sizeof("image/avif,") - 1;
42 #if BUILDFLAG(ENABLE_AV1_DECODER)
43   static const char* header = base::FeatureList::IsEnabled(features::kAVIF)
44                                   ? kImageAcceptHeaderWithAvif
45                                   : kImageAcceptHeaderWithAvif + kOffset;
46 #else
47   static const char* header = kImageAcceptHeaderWithAvif + kOffset;
48 #endif
49   return header;
50 }
51 
52 namespace {
53 
54 constexpr char kStylesheetAcceptHeader[] = "text/css,*/*;q=0.1";
55 
56 // TODO(yhirano): Unify these with variables in
57 // content/public/common/content_constants.h.
58 constexpr char kCorsExemptPurposeHeaderName[] = "Purpose";
59 constexpr char kCorsExemptRequestedWithHeaderName[] = "X-Requested-With";
60 
61 // This is complementary to ConvertNetPriorityToWebKitPriority, defined in
62 // service_worker_context_client.cc.
ConvertWebKitPriorityToNetPriority(WebURLRequest::Priority priority)63 net::RequestPriority ConvertWebKitPriorityToNetPriority(
64     WebURLRequest::Priority priority) {
65   switch (priority) {
66     case WebURLRequest::Priority::kVeryHigh:
67       return net::HIGHEST;
68 
69     case WebURLRequest::Priority::kHigh:
70       return net::MEDIUM;
71 
72     case WebURLRequest::Priority::kMedium:
73       return net::LOW;
74 
75     case WebURLRequest::Priority::kLow:
76       return net::LOWEST;
77 
78     case WebURLRequest::Priority::kVeryLow:
79       return net::IDLE;
80 
81     case WebURLRequest::Priority::kUnresolved:
82     default:
83       NOTREACHED();
84       return net::LOW;
85   }
86 }
87 
88 // TODO(yhirano) Dedupe this and the same-name function in
89 // web_url_request_util.cc.
TrimLWSAndCRLF(const base::StringPiece & input)90 std::string TrimLWSAndCRLF(const base::StringPiece& input) {
91   base::StringPiece string = net::HttpUtil::TrimLWS(input);
92   const char* begin = string.data();
93   const char* end = string.data() + string.size();
94   while (begin < end && (end[-1] == '\r' || end[-1] == '\n'))
95     --end;
96   return std::string(base::StringPiece(begin, end - begin));
97 }
98 
RequestContextToResourceType(mojom::blink::RequestContextType request_context)99 mojom::ResourceType RequestContextToResourceType(
100     mojom::blink::RequestContextType request_context) {
101   switch (request_context) {
102     // CSP report
103     case mojom::blink::RequestContextType::CSP_REPORT:
104       return mojom::ResourceType::kCspReport;
105 
106     // Favicon
107     case mojom::blink::RequestContextType::FAVICON:
108       return mojom::ResourceType::kFavicon;
109 
110     // Font
111     case mojom::blink::RequestContextType::FONT:
112       return mojom::ResourceType::kFontResource;
113 
114     // Image
115     case mojom::blink::RequestContextType::IMAGE:
116     case mojom::blink::RequestContextType::IMAGE_SET:
117       return mojom::ResourceType::kImage;
118 
119     // Media
120     case mojom::blink::RequestContextType::AUDIO:
121     case mojom::blink::RequestContextType::VIDEO:
122       return mojom::ResourceType::kMedia;
123 
124     // Object
125     case mojom::blink::RequestContextType::EMBED:
126     case mojom::blink::RequestContextType::OBJECT:
127       return mojom::ResourceType::kObject;
128 
129     // Ping
130     case mojom::blink::RequestContextType::BEACON:
131     case mojom::blink::RequestContextType::PING:
132       return mojom::ResourceType::kPing;
133 
134     // Subresource of plugins
135     case mojom::blink::RequestContextType::PLUGIN:
136       return mojom::ResourceType::kPluginResource;
137 
138     // Prefetch
139     case mojom::blink::RequestContextType::PREFETCH:
140       return mojom::ResourceType::kPrefetch;
141 
142     // Script
143     case mojom::blink::RequestContextType::IMPORT:
144     case mojom::blink::RequestContextType::SCRIPT:
145       return mojom::ResourceType::kScript;
146 
147     // Style
148     case mojom::blink::RequestContextType::XSLT:
149     case mojom::blink::RequestContextType::STYLE:
150       return mojom::ResourceType::kStylesheet;
151 
152     // Subresource
153     case mojom::blink::RequestContextType::DOWNLOAD:
154     case mojom::blink::RequestContextType::MANIFEST:
155     case mojom::blink::RequestContextType::SUBRESOURCE:
156       return mojom::ResourceType::kSubResource;
157 
158     // TextTrack
159     case mojom::blink::RequestContextType::TRACK:
160       return mojom::ResourceType::kMedia;
161 
162     // Workers
163     case mojom::blink::RequestContextType::SERVICE_WORKER:
164       return mojom::ResourceType::kServiceWorker;
165     case mojom::blink::RequestContextType::SHARED_WORKER:
166       return mojom::ResourceType::kSharedWorker;
167     case mojom::blink::RequestContextType::WORKER:
168       return mojom::ResourceType::kWorker;
169 
170     // Unspecified
171     case mojom::blink::RequestContextType::INTERNAL:
172     case mojom::blink::RequestContextType::UNSPECIFIED:
173       return mojom::ResourceType::kSubResource;
174 
175     // XHR
176     case mojom::blink::RequestContextType::EVENT_SOURCE:
177     case mojom::blink::RequestContextType::FETCH:
178     case mojom::blink::RequestContextType::XML_HTTP_REQUEST:
179       return mojom::ResourceType::kXhr;
180 
181     // Navigation requests should not go through WebURLLoader.
182     case mojom::blink::RequestContextType::FORM:
183     case mojom::blink::RequestContextType::HYPERLINK:
184     case mojom::blink::RequestContextType::LOCATION:
185     case mojom::blink::RequestContextType::FRAME:
186     case mojom::blink::RequestContextType::IFRAME:
187       NOTREACHED();
188       return mojom::ResourceType::kSubResource;
189 
190     default:
191       NOTREACHED();
192       return mojom::ResourceType::kSubResource;
193   }
194 }
195 
PopulateResourceRequestBody(const EncodedFormData & src,network::ResourceRequestBody * dest)196 void PopulateResourceRequestBody(const EncodedFormData& src,
197                                  network::ResourceRequestBody* dest) {
198   for (const auto& element : src.Elements()) {
199     switch (element.type_) {
200       case FormDataElement::kData:
201         dest->AppendBytes(element.data_.data(), element.data_.size());
202         break;
203       case FormDataElement::kEncodedFile:
204         if (element.file_length_ == -1) {
205           dest->AppendFileRange(
206               WebStringToFilePath(element.filename_), 0,
207               std::numeric_limits<uint64_t>::max(),
208               element.expected_file_modification_time_.value_or(base::Time()));
209         } else {
210           dest->AppendFileRange(
211               WebStringToFilePath(element.filename_),
212               static_cast<uint64_t>(element.file_start_),
213               static_cast<uint64_t>(element.file_length_),
214               element.expected_file_modification_time_.value_or(base::Time()));
215         }
216         break;
217       case FormDataElement::kEncodedBlob: {
218         DCHECK(element.optional_blob_data_handle_);
219         mojo::Remote<mojom::Blob> blob_remote(mojo::PendingRemote<mojom::Blob>(
220             element.optional_blob_data_handle_->CloneBlobRemote().PassPipe(),
221             mojom::Blob::Version_));
222         mojo::PendingRemote<network::mojom::DataPipeGetter>
223             data_pipe_getter_remote;
224         blob_remote->AsDataPipeGetter(
225             data_pipe_getter_remote.InitWithNewPipeAndPassReceiver());
226         dest->AppendDataPipe(std::move(data_pipe_getter_remote));
227         break;
228       }
229       case FormDataElement::kDataPipe: {
230         // Convert network::mojom::blink::DataPipeGetter to
231         // network::mojom::DataPipeGetter through a raw message pipe.
232         mojo::PendingRemote<network::mojom::blink::DataPipeGetter>
233             pending_data_pipe_getter;
234         element.data_pipe_getter_->GetDataPipeGetter()->Clone(
235             pending_data_pipe_getter.InitWithNewPipeAndPassReceiver());
236         dest->AppendDataPipe(
237             mojo::PendingRemote<network::mojom::DataPipeGetter>(
238                 pending_data_pipe_getter.PassPipe(), 0u));
239         break;
240       }
241     }
242   }
243 }
244 
245 }  // namespace
246 
PopulateResourceRequest(const ResourceRequestHead & src,ResourceRequestBody src_body,network::ResourceRequest * dest)247 void PopulateResourceRequest(const ResourceRequestHead& src,
248                              ResourceRequestBody src_body,
249                              network::ResourceRequest* dest) {
250   dest->method = src.HttpMethod().Latin1();
251   dest->url = src.Url();
252   dest->site_for_cookies = src.SiteForCookies();
253   dest->upgrade_if_insecure = src.UpgradeIfInsecure();
254   dest->is_revalidating = src.IsRevalidating();
255   if (src.RequestorOrigin()->ToString() == "null") {
256     // "file:" origin is treated like an opaque unique origin when
257     // allow-file-access-from-files is not specified. Such origin is not opaque
258     // (i.e., IsOpaque() returns false) but still serializes to "null". Derive a
259     // new opaque origin so that downstream consumers can make use of the
260     // origin's precursor.
261     dest->request_initiator =
262         src.RequestorOrigin()->DeriveNewOpaqueOrigin()->ToUrlOrigin();
263   } else {
264     dest->request_initiator = src.RequestorOrigin()->ToUrlOrigin();
265   }
266   if (src.IsolatedWorldOrigin()) {
267     dest->isolated_world_origin = src.IsolatedWorldOrigin()->ToUrlOrigin();
268   }
269   dest->referrer = WebStringToGURL(src.ReferrerString());
270 
271   // "default" referrer policy has already been resolved.
272   DCHECK_NE(src.GetReferrerPolicy(), network::mojom::ReferrerPolicy::kDefault);
273   dest->referrer_policy =
274       network::ReferrerPolicyForUrlRequest(src.GetReferrerPolicy());
275 
276   for (const auto& item : src.HttpHeaderFields()) {
277     const std::string name = item.key.Latin1();
278     const std::string value = TrimLWSAndCRLF(item.value.Latin1());
279     dest->headers.SetHeader(name, value);
280   }
281   // Set X-Requested-With header to cors_exempt_headers rather than headers to
282   // be exempted from CORS checks.
283   if (!src.GetRequestedWithHeader().IsEmpty()) {
284     dest->cors_exempt_headers.SetHeader(kCorsExemptRequestedWithHeaderName,
285                                         src.GetRequestedWithHeader().Utf8());
286   }
287   // Set Purpose header to cors_exempt_headers rather than headers to be
288   // exempted from CORS checks.
289   if (!src.GetPurposeHeader().IsEmpty()) {
290     dest->cors_exempt_headers.SetHeader(kCorsExemptPurposeHeaderName,
291                                         src.GetPurposeHeader().Utf8());
292   }
293 
294   // TODO(yhirano): Remove this WrappedResourceRequest.
295   dest->load_flags = WrappedResourceRequest(ResourceRequest(src))
296                          .GetLoadFlagsForWebUrlRequest();
297   dest->recursive_prefetch_token = src.RecursivePrefetchToken();
298   dest->priority = ConvertWebKitPriorityToNetPriority(src.Priority());
299   dest->should_reset_appcache = src.ShouldResetAppCache();
300   dest->is_external_request = src.IsExternalRequest();
301   dest->cors_preflight_policy = src.CorsPreflightPolicy();
302   dest->skip_service_worker = src.GetSkipServiceWorker();
303   dest->mode = src.GetMode();
304   dest->destination = src.GetRequestDestination();
305   dest->credentials_mode = src.GetCredentialsMode();
306   dest->redirect_mode = src.GetRedirectMode();
307   dest->fetch_integrity = src.GetFetchIntegrity().Utf8();
308 
309   mojom::ResourceType resource_type =
310       RequestContextToResourceType(src.GetRequestContext());
311 
312   // TODO(kinuko): Deprecate this.
313   dest->resource_type = static_cast<int>(resource_type);
314 
315   if (resource_type == mojom::ResourceType::kXhr &&
316       (dest->url.has_username() || dest->url.has_password())) {
317     dest->do_not_prompt_for_login = true;
318   }
319   if (resource_type == mojom::ResourceType::kPrefetch ||
320       resource_type == mojom::ResourceType::kFavicon) {
321     dest->do_not_prompt_for_login = true;
322   }
323 
324   dest->keepalive = src.GetKeepalive();
325   dest->has_user_gesture = src.HasUserGesture();
326   dest->enable_load_timing = true;
327   dest->enable_upload_progress = src.ReportUploadProgress();
328   dest->report_raw_headers = src.ReportRawHeaders();
329   // TODO(ryansturm): Remove dest->previews_state once it is no
330   // longer used in a network delegate. https://crbug.com/842233
331   dest->previews_state = static_cast<int>(src.GetPreviewsState());
332   dest->throttling_profile_id = src.GetDevToolsToken();
333   dest->trust_token_params = ConvertTrustTokenParams(src.TrustTokenParams());
334 
335   if (base::UnguessableToken window_id = src.GetFetchWindowId())
336     dest->fetch_window_id = base::make_optional(window_id);
337 
338   if (src.GetDevToolsId().has_value()) {
339     dest->devtools_request_id = src.GetDevToolsId().value().Ascii();
340   }
341 
342   if (src.GetDevToolsStackId().has_value()) {
343     dest->devtools_stack_id = src.GetDevToolsStackId().value().Ascii();
344   }
345 
346   if (src.IsSignedExchangePrefetchCacheEnabled()) {
347     DCHECK_EQ(src.GetRequestContext(),
348               mojom::blink::RequestContextType::PREFETCH);
349     dest->is_signed_exchange_prefetch_cache_enabled = true;
350   }
351 
352   dest->is_fetch_like_api = src.IsFetchLikeAPI();
353 
354   if (const EncodedFormData* body = src_body.FormBody().get()) {
355     DCHECK_NE(dest->method, net::HttpRequestHeaders::kGetMethod);
356     DCHECK_NE(dest->method, net::HttpRequestHeaders::kHeadMethod);
357     dest->request_body = base::MakeRefCounted<network::ResourceRequestBody>();
358 
359     PopulateResourceRequestBody(*body, dest->request_body.get());
360   } else if (src_body.StreamBody().is_valid()) {
361     DCHECK_NE(dest->method, net::HttpRequestHeaders::kGetMethod);
362     DCHECK_NE(dest->method, net::HttpRequestHeaders::kHeadMethod);
363     mojo::PendingRemote<network::mojom::blink::ChunkedDataPipeGetter>
364         stream_body = src_body.TakeStreamBody();
365     dest->request_body = base::MakeRefCounted<network::ResourceRequestBody>();
366     mojo::PendingRemote<network::mojom::ChunkedDataPipeGetter>
367         network_stream_body(stream_body.PassPipe(), 0u);
368     dest->request_body->SetToReadOnceStream(std::move(network_stream_body));
369     dest->request_body->SetAllowHTTP1ForStreamingUpload(
370         src.AllowHTTP1ForStreamingUpload());
371   }
372 
373   if (resource_type == mojom::ResourceType::kStylesheet) {
374     dest->headers.SetHeader(net::HttpRequestHeaders::kAccept,
375                             kStylesheetAcceptHeader);
376   } else if (resource_type == mojom::ResourceType::kImage ||
377              resource_type == mojom::ResourceType::kFavicon) {
378     dest->headers.SetHeaderIfMissing(net::HttpRequestHeaders::kAccept,
379                                      ImageAcceptHeader());
380   } else {
381     // Calling SetHeaderIfMissing() instead of SetHeader() because JS can
382     // manually set an accept header on an XHR.
383     dest->headers.SetHeaderIfMissing(net::HttpRequestHeaders::kAccept,
384                                      network::kDefaultAcceptHeaderValue);
385   }
386 }
387 
388 }  // namespace blink
389