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