net/fetch/fetch_params.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::sync::{Arc, Mutex};
6
7use content_security_policy as csp;
8use net_traits::request::{PreloadEntry, PreloadId, PreloadKey, Request, RequestClient};
9use net_traits::response::Response;
10use rustc_hash::FxHashMap;
11use tokio::sync::oneshot::Receiver as TokioReceiver;
12
13/// <https://fetch.spec.whatwg.org/#fetch-params-preloaded-response-candidate>
14pub enum PreloadResponseCandidate {
15 None,
16 Pending(TokioReceiver<Response>, PreloadId),
17 Response(Box<Response>, PreloadId),
18}
19
20impl PreloadResponseCandidate {
21 /// Part of Step 12 of <https://fetch.spec.whatwg.org/#concept-main-fetch>
22 pub(crate) async fn response(&mut self) -> Option<(Response, PreloadId)> {
23 // Step 3. Return fetchParams’s preloaded response candidate.
24 match self {
25 PreloadResponseCandidate::None => None,
26 // Step 2. Assert: fetchParams’s preloaded response candidate is a response.
27 PreloadResponseCandidate::Response(response, preload_id) => {
28 Some((*response.clone(), preload_id.clone()))
29 },
30 // Step 1. Wait until fetchParams’s preloaded response candidate is not "pending".
31 PreloadResponseCandidate::Pending(receiver, preload_id) => receiver
32 .await
33 .ok()
34 .map(|response| (response, preload_id.clone())),
35 }
36 }
37}
38
39/// <https://fetch.spec.whatwg.org/#fetch-params>
40pub struct FetchParams {
41 /// <https://fetch.spec.whatwg.org/#fetch-params-request>
42 pub request: Request,
43 /// <https://fetch.spec.whatwg.org/#fetch-params-preloaded-response-candidate>
44 pub preload_response_candidate: PreloadResponseCandidate,
45}
46
47impl FetchParams {
48 pub fn new(request: Request) -> FetchParams {
49 FetchParams {
50 request,
51 preload_response_candidate: PreloadResponseCandidate::None,
52 }
53 }
54}
55
56pub type SharedPreloadedResources = Arc<Mutex<FxHashMap<PreloadId, PreloadEntry>>>;
57
58pub trait ConsumePreloadedResources {
59 fn consume_preloaded_resource(
60 &self,
61 request: &Request,
62 preloaded_resources: SharedPreloadedResources,
63 ) -> Option<PreloadResponseCandidate>;
64}
65
66impl ConsumePreloadedResources for RequestClient {
67 /// <https://html.spec.whatwg.org/multipage/#consume-a-preloaded-resource>
68 fn consume_preloaded_resource(
69 &self,
70 request: &Request,
71 preloaded_resources: SharedPreloadedResources,
72 ) -> Option<PreloadResponseCandidate> {
73 // Step 1. Let key be a preload key whose URL is url,
74 // destination is destination, mode is mode, and credentials mode is credentialsMode.
75 let key = PreloadKey {
76 url: request.url().clone(),
77 destination: request.destination,
78 mode: request.mode.clone(),
79 credentials_mode: request.credentials_mode,
80 };
81 // Step 2. Let preloads be window's associated Document's map of preloaded resources.
82 // Step 3. If key does not exist in preloads, then return false.
83 let preload_id = self.preloaded_resources.get(&key)?;
84
85 // Step 4. Let entry be preloads[key].
86 let mut preloaded_resources_lock = preloaded_resources.lock();
87 let preloads = preloaded_resources_lock.as_mut().unwrap();
88 let preload_entry = preloads.get_mut(preload_id)?;
89
90 // Step 5. Let consumerIntegrityMetadata be the result of parsing integrityMetadata.
91 let consumer_integrity_metadata =
92 csp::parse_subresource_integrity_metadata(&request.integrity_metadata);
93 // Step 6. Let preloadIntegrityMetadata be the result of parsing entry's integrity metadata.
94 let preload_integrity_metadata =
95 csp::parse_subresource_integrity_metadata(&preload_entry.integrity_metadata);
96 // Step 7. If none of the following conditions apply:
97 if !(
98 // consumerIntegrityMetadata is no metadata;
99 consumer_integrity_metadata == csp::SubresourceIntegrityMetadata::NoMetadata
100 // consumerIntegrityMetadata is equal to preloadIntegrityMetadata; or
101 || consumer_integrity_metadata == preload_integrity_metadata
102 ) {
103 // then return false.
104 return None;
105 }
106
107 // Step 8. Remove preloads[key].
108 // Note: `self.preloads` is an immutable copy of the list of preloads
109 // that is not shared with any other running fetch.
110 // Instead, we defer removing the associated PreloadId->PreloadEntry
111 // map entry until the entire response has been preloaded, since
112 // it stores the Sender which will be used to transmit the complete
113 // response.
114
115 // Step 10. Otherwise, call onResponseAvailable with entry's response.
116 if let Some(response) = preload_entry.response.as_ref() {
117 Some(PreloadResponseCandidate::Response(
118 Box::new(response.clone()),
119 preload_id.clone(),
120 ))
121 } else {
122 // Step 9. If entry's response is null, then set entry's on response available to onResponseAvailable.
123 let (sender, receiver) = tokio::sync::oneshot::channel();
124 preload_entry.on_response_available = Some(sender);
125 // Step 11. Return true.
126 Some(PreloadResponseCandidate::Pending(
127 receiver,
128 preload_id.clone(),
129 ))
130 }
131 }
132}