net_traits/
response.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
5//! The [Response](https://fetch.spec.whatwg.org/#responses) object
6//! resulting from a [fetch operation](https://fetch.spec.whatwg.org/#concept-fetch)
7use std::sync::atomic::AtomicBool;
8
9use http::HeaderMap;
10use hyper_serde::Serde;
11use malloc_size_of_derive::MallocSizeOf;
12use parking_lot::Mutex;
13use serde::{Deserialize, Serialize};
14use servo_arc::Arc;
15use servo_url::ServoUrl;
16
17use crate::fetch::headers::extract_mime_type_as_mime;
18use crate::http_status::HttpStatus;
19use crate::resource_fetch_timing::{ResourceFetchTimingContainer, ResourceTimingType};
20use crate::{
21    FetchMetadata, FilteredMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming,
22    TlsSecurityInfo,
23};
24
25/// [Response type](https://fetch.spec.whatwg.org/#concept-response-type)
26#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
27pub enum ResponseType {
28    Basic,
29    Cors,
30    Default,
31    Error(NetworkError),
32    Opaque,
33    OpaqueRedirect,
34}
35
36/// [Response termination reason](https://fetch.spec.whatwg.org/#concept-response-termination-reason)
37#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)]
38pub enum TerminationReason {
39    EndUserAbort,
40    Fatal,
41    Timeout,
42}
43
44/// The response body can still be pushed to after fetch
45/// This provides a way to store unfinished response bodies
46#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
47pub enum ResponseBody {
48    Empty, // XXXManishearth is this necessary, or is Done(vec![]) enough?
49    Receiving(Vec<u8>),
50    Done(Vec<u8>),
51}
52
53impl ResponseBody {
54    pub fn is_done(&self) -> bool {
55        match *self {
56            ResponseBody::Done(..) => true,
57            ResponseBody::Empty | ResponseBody::Receiving(..) => false,
58        }
59    }
60}
61
62/// <https://fetch.spec.whatwg.org/#response-redirect-taint>
63#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
64pub enum RedirectTaint {
65    #[default]
66    SameOrigin,
67    SameSite,
68    CrossSite,
69}
70
71/// [Cache state](https://fetch.spec.whatwg.org/#concept-response-cache-state)
72#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)]
73pub enum CacheState {
74    None,
75    Local,
76    Validated,
77    Partial,
78}
79
80/// [Https state](https://fetch.spec.whatwg.org/#concept-response-https-state)
81#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
82pub enum HttpsState {
83    None,
84    Deprecated,
85    Modern,
86}
87
88#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
89pub struct ResponseInit {
90    pub url: ServoUrl,
91    #[serde(
92        deserialize_with = "::hyper_serde::deserialize",
93        serialize_with = "::hyper_serde::serialize"
94    )]
95    pub headers: HeaderMap,
96    pub status_code: u16,
97    pub referrer: Option<ServoUrl>,
98    pub location_url: Option<Result<ServoUrl, String>>,
99}
100
101/// A [Response](https://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec
102#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
103pub struct Response {
104    pub response_type: ResponseType,
105    pub termination_reason: Option<TerminationReason>,
106    url: Option<ServoUrl>,
107    pub url_list: Vec<ServoUrl>,
108    pub status: HttpStatus,
109    #[serde(
110        deserialize_with = "::hyper_serde::deserialize",
111        serialize_with = "::hyper_serde::serialize"
112    )]
113    pub headers: HeaderMap,
114    #[conditional_malloc_size_of]
115    pub body: Arc<Mutex<ResponseBody>>,
116    pub cache_state: CacheState,
117    pub https_state: HttpsState,
118    pub tls_security_info: Option<TlsSecurityInfo>,
119    pub referrer: Option<ServoUrl>,
120    /// <https://fetch.spec.whatwg.org/#response-redirect-taint>
121    pub redirect_taint: RedirectTaint,
122    pub referrer_policy: ReferrerPolicy,
123    /// [CORS-exposed header-name list](https://fetch.spec.whatwg.org/#concept-response-cors-exposed-header-name-list)
124    pub cors_exposed_header_name_list: Vec<String>,
125    /// [Location URL](https://fetch.spec.whatwg.org/#concept-response-location-url)
126    pub location_url: Option<Result<ServoUrl, String>>,
127    /// [Internal response](https://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response
128    /// is a filtered response
129    pub internal_response: Option<Box<Response>>,
130    /// whether or not to try to return the internal_response when asked for actual_response
131    pub return_internal: bool,
132    /// <https://fetch.spec.whatwg.org/#concept-response-aborted>
133    #[conditional_malloc_size_of]
134    pub aborted: Arc<AtomicBool>,
135    /// track network metrics
136    pub resource_timing: ResourceFetchTimingContainer,
137
138    /// <https://fetch.spec.whatwg.org/#concept-response-range-requested-flag>
139    pub range_requested: bool,
140
141    /// <https://fetch.spec.whatwg.org/#response-request-includes-credentials>
142    /// A response has an associated request-includes-credentials, which is initially true.
143    pub request_includes_credentials: bool,
144}
145
146impl Response {
147    pub fn new(url: ServoUrl, resource_timing: ResourceFetchTiming) -> Response {
148        Response {
149            response_type: ResponseType::Default,
150            termination_reason: None,
151            url: Some(url),
152            url_list: vec![],
153            status: HttpStatus::default(),
154            headers: HeaderMap::new(),
155            body: Arc::new(Mutex::new(ResponseBody::Empty)),
156            cache_state: CacheState::None,
157            https_state: HttpsState::None,
158            tls_security_info: None,
159            referrer: None,
160            referrer_policy: ReferrerPolicy::EmptyString,
161            cors_exposed_header_name_list: vec![],
162            location_url: None,
163            internal_response: None,
164            return_internal: true,
165            aborted: Arc::new(AtomicBool::new(false)),
166            resource_timing: resource_timing.into(),
167            range_requested: false,
168            request_includes_credentials: true,
169            redirect_taint: Default::default(),
170        }
171    }
172
173    pub fn from_init(init: ResponseInit, resource_timing_type: ResourceTimingType) -> Response {
174        let mut res = Response::new(init.url, ResourceFetchTiming::new(resource_timing_type));
175        res.location_url = init.location_url;
176        res.headers = init.headers;
177        res.referrer = init.referrer;
178        res.status = HttpStatus::new_raw(init.status_code, vec![]);
179        res
180    }
181
182    pub fn network_error(e: NetworkError) -> Response {
183        Response {
184            response_type: ResponseType::Error(e),
185            termination_reason: None,
186            url: None,
187            url_list: vec![],
188            status: HttpStatus::new_error(),
189            headers: HeaderMap::new(),
190            body: Arc::new(Mutex::new(ResponseBody::Empty)),
191            cache_state: CacheState::None,
192            https_state: HttpsState::None,
193            tls_security_info: None,
194            referrer: None,
195            referrer_policy: ReferrerPolicy::EmptyString,
196            cors_exposed_header_name_list: vec![],
197            location_url: None,
198            internal_response: None,
199            return_internal: true,
200            aborted: Arc::new(AtomicBool::new(false)),
201            resource_timing: ResourceFetchTiming::new(ResourceTimingType::Error).into(),
202            range_requested: false,
203            request_includes_credentials: true,
204            redirect_taint: Default::default(),
205        }
206    }
207
208    pub fn url(&self) -> Option<&ServoUrl> {
209        self.url.as_ref()
210    }
211
212    pub fn is_network_error(&self) -> bool {
213        matches!(self.response_type, ResponseType::Error(..))
214    }
215
216    pub fn get_network_error(&self) -> Option<&NetworkError> {
217        match self.response_type {
218            ResponseType::Error(ref e) => Some(e),
219            _ => None,
220        }
221    }
222
223    pub fn set_network_error(&mut self, network_error: NetworkError) {
224        self.response_type = ResponseType::Error(network_error);
225    }
226
227    pub fn actual_response(&self) -> &Response {
228        if self.return_internal && self.internal_response.is_some() {
229            self.internal_response.as_ref().unwrap()
230        } else {
231            self
232        }
233    }
234
235    pub fn actual_response_mut(&mut self) -> &mut Response {
236        if self.return_internal && self.internal_response.is_some() {
237            self.internal_response.as_mut().unwrap()
238        } else {
239            self
240        }
241    }
242
243    pub fn to_actual(self) -> Response {
244        if self.return_internal && self.internal_response.is_some() {
245            *self.internal_response.unwrap()
246        } else {
247            self
248        }
249    }
250
251    pub fn get_resource_timing(&self) -> &ResourceFetchTimingContainer {
252        &self.resource_timing
253    }
254
255    /// Convert to a filtered response, of type `filter_type`.
256    /// Do not use with type Error or Default
257    #[rustfmt::skip]
258    pub fn to_filtered(self, filter_type: ResponseType) -> Response {
259        match filter_type {
260            ResponseType::Default |
261            ResponseType::Error(..) => panic!(),
262            _ => (),
263        }
264
265        let old_response = self.to_actual();
266
267        if let ResponseType::Error(e) = old_response.response_type {
268            return Response::network_error(e);
269        }
270
271        let old_headers = old_response.headers.clone();
272        let exposed_headers = old_response.cors_exposed_header_name_list.clone();
273        let mut response = old_response.clone();
274        response.internal_response = Some(Box::new(old_response));
275        response.response_type = filter_type;
276
277        match response.response_type {
278            ResponseType::Default |
279            ResponseType::Error(..) => unreachable!(),
280
281            ResponseType::Basic => {
282                let headers = old_headers.iter().filter(|(name, _)| {
283                    !matches!(&*name.as_str().to_ascii_lowercase(), "set-cookie" | "set-cookie2")
284                }).map(|(n, v)| (n.clone(), v.clone())).collect();
285                response.headers = headers;
286            },
287
288            ResponseType::Cors => {
289                let headers = old_headers.iter().filter(|(name, _)| {
290                    match &*name.as_str().to_ascii_lowercase() {
291                        "cache-control" | "content-language" | "content-length" | "content-type" |
292                        "expires" | "last-modified" | "pragma" => true,
293                        "set-cookie" | "set-cookie2" => false,
294                        header => {
295                            exposed_headers.iter().any(|h| *header == h.as_str().to_ascii_lowercase())
296                        }
297                    }
298                }).map(|(n, v)| (n.clone(), v.clone())).collect();
299                response.headers = headers;
300            },
301
302            ResponseType::Opaque => {
303                response.url_list = vec![];
304                response.url = None;
305                response.headers = HeaderMap::new();
306                response.status = HttpStatus::new_error();
307                response.body = Arc::new(Mutex::new(ResponseBody::Empty));
308                response.cache_state = CacheState::None;
309            },
310
311            ResponseType::OpaqueRedirect => {
312                response.headers = HeaderMap::new();
313                response.status = HttpStatus::new_error();
314                response.body = Arc::new(Mutex::new(ResponseBody::Empty));
315                response.cache_state = CacheState::None;
316            },
317        }
318
319        response
320    }
321
322    pub fn metadata(&self) -> Result<FetchMetadata, NetworkError> {
323        fn init_metadata(response: &Response, url: &ServoUrl) -> Metadata {
324            let mut metadata = Metadata::default(url.clone());
325            metadata.set_content_type(extract_mime_type_as_mime(&response.headers).as_ref());
326            metadata.location_url.clone_from(&response.location_url);
327            metadata.headers = Some(Serde(response.headers.clone()));
328            metadata.status.clone_from(&response.status);
329            metadata.https_state = response.https_state;
330            metadata.referrer.clone_from(&response.referrer);
331            metadata.referrer_policy = response.referrer_policy;
332            metadata.redirected = response.actual_response().url_list.len() > 1;
333            metadata
334                .tls_security_info
335                .clone_from(&response.tls_security_info);
336            metadata
337        }
338
339        if let Some(error) = self.get_network_error() {
340            return Err(error.clone());
341        }
342
343        let metadata = self.url.as_ref().map(|url| init_metadata(self, url));
344
345        if let Some(ref response) = self.internal_response {
346            match response.url {
347                Some(ref url) => {
348                    let unsafe_metadata = init_metadata(response, url);
349
350                    match self.response_type {
351                        ResponseType::Basic => Ok(FetchMetadata::Filtered {
352                            filtered: FilteredMetadata::Basic(metadata.unwrap()),
353                            unsafe_: unsafe_metadata,
354                        }),
355                        ResponseType::Cors => Ok(FetchMetadata::Filtered {
356                            filtered: FilteredMetadata::Cors(metadata.unwrap()),
357                            unsafe_: unsafe_metadata,
358                        }),
359                        ResponseType::Default => unreachable!(),
360                        ResponseType::Error(ref network_err) => Err(network_err.clone()),
361                        ResponseType::Opaque => Ok(FetchMetadata::Filtered {
362                            filtered: FilteredMetadata::Opaque,
363                            unsafe_: unsafe_metadata,
364                        }),
365                        ResponseType::OpaqueRedirect => Ok(FetchMetadata::Filtered {
366                            filtered: FilteredMetadata::OpaqueRedirect(url.clone()),
367                            unsafe_: unsafe_metadata,
368                        }),
369                    }
370                },
371                None => Err(NetworkError::ResourceLoadError(
372                    "No url found in unsafe response".to_owned(),
373                )),
374            }
375        } else {
376            assert_eq!(self.response_type, ResponseType::Default);
377            Ok(FetchMetadata::Unfiltered(metadata.unwrap()))
378        }
379    }
380}