/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! The [Response](https://fetch.spec.whatwg.org/#responses) object //! resulting from a [fetch operation](https://fetch.spec.whatwg.org/#concept-fetch) use std::sync::atomic::AtomicBool; use std::sync::Mutex; use headers::{ContentType, HeaderMapExt}; use http::{HeaderMap, StatusCode}; use hyper_serde::Serde; use malloc_size_of_derive::MallocSizeOf; use serde::{Deserialize, Serialize}; use servo_arc::Arc; use servo_url::ServoUrl; use crate::{ FetchMetadata, FilteredMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming, ResourceTimingType, }; /// [Response type](https://fetch.spec.whatwg.org/#concept-response-type) #[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum ResponseType { Basic, Cors, Default, Error(NetworkError), Opaque, OpaqueRedirect, } /// [Response termination reason](https://fetch.spec.whatwg.org/#concept-response-termination-reason) #[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)] pub enum TerminationReason { EndUserAbort, Fatal, Timeout, } /// The response body can still be pushed to after fetch /// This provides a way to store unfinished response bodies #[derive(Clone, Debug, MallocSizeOf, PartialEq)] pub enum ResponseBody { Empty, // XXXManishearth is this necessary, or is Done(vec![]) enough? Receiving(Vec), Done(Vec), } impl ResponseBody { pub fn is_done(&self) -> bool { match *self { ResponseBody::Done(..) => true, ResponseBody::Empty | ResponseBody::Receiving(..) => false, } } } /// [Cache state](https://fetch.spec.whatwg.org/#concept-response-cache-state) #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub enum CacheState { None, Local, Validated, Partial, } /// [Https state](https://fetch.spec.whatwg.org/#concept-response-https-state) #[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum HttpsState { None, Deprecated, Modern, } #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct ResponseInit { pub url: ServoUrl, #[serde( deserialize_with = "::hyper_serde::deserialize", serialize_with = "::hyper_serde::serialize" )] #[ignore_malloc_size_of = "Defined in hyper"] pub headers: HeaderMap, pub status_code: u16, pub referrer: Option, pub location_url: Option>, } /// A [Response](https://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec #[derive(Clone, Debug, MallocSizeOf)] pub struct Response { pub response_type: ResponseType, pub termination_reason: Option, url: Option, pub url_list: Vec, /// `None` can be considered a StatusCode of `0`. #[ignore_malloc_size_of = "Defined in hyper"] pub status: Option<(StatusCode, String)>, pub raw_status: Option<(u16, Vec)>, #[ignore_malloc_size_of = "Defined in hyper"] pub headers: HeaderMap, #[ignore_malloc_size_of = "Mutex heap size undefined"] pub body: Arc>, pub cache_state: CacheState, pub https_state: HttpsState, pub referrer: Option, pub referrer_policy: Option, /// [CORS-exposed header-name list](https://fetch.spec.whatwg.org/#concept-response-cors-exposed-header-name-list) pub cors_exposed_header_name_list: Vec, /// [Location URL](https://fetch.spec.whatwg.org/#concept-response-location-url) pub location_url: Option>, /// [Internal response](https://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response /// is a filtered response pub internal_response: Option>, /// whether or not to try to return the internal_response when asked for actual_response pub return_internal: bool, /// #[ignore_malloc_size_of = "AtomicBool heap size undefined"] pub aborted: Arc, /// track network metrics #[ignore_malloc_size_of = "Mutex heap size undefined"] pub resource_timing: Arc>, } impl Response { pub fn new(url: ServoUrl, resource_timing: ResourceFetchTiming) -> Response { Response { response_type: ResponseType::Default, termination_reason: None, url: Some(url), url_list: vec![], status: Some((StatusCode::OK, "".to_string())), raw_status: Some((200, b"".to_vec())), headers: HeaderMap::new(), body: Arc::new(Mutex::new(ResponseBody::Empty)), cache_state: CacheState::None, https_state: HttpsState::None, referrer: None, referrer_policy: None, cors_exposed_header_name_list: vec![], location_url: None, internal_response: None, return_internal: true, aborted: Arc::new(AtomicBool::new(false)), resource_timing: Arc::new(Mutex::new(resource_timing)), } } pub fn from_init(init: ResponseInit, resource_timing_type: ResourceTimingType) -> Response { let mut res = Response::new(init.url, ResourceFetchTiming::new(resource_timing_type)); res.location_url = init.location_url; res.headers = init.headers; res.referrer = init.referrer; res.status = StatusCode::from_u16(init.status_code) .map(|s| (s, s.to_string())) .ok(); res } pub fn network_error(e: NetworkError) -> Response { Response { response_type: ResponseType::Error(e), termination_reason: None, url: None, url_list: vec![], status: None, raw_status: None, headers: HeaderMap::new(), body: Arc::new(Mutex::new(ResponseBody::Empty)), cache_state: CacheState::None, https_state: HttpsState::None, referrer: None, referrer_policy: None, cors_exposed_header_name_list: vec![], location_url: None, internal_response: None, return_internal: true, aborted: Arc::new(AtomicBool::new(false)), resource_timing: Arc::new(Mutex::new(ResourceFetchTiming::new( ResourceTimingType::Error, ))), } } pub fn url(&self) -> Option<&ServoUrl> { self.url.as_ref() } pub fn is_network_error(&self) -> bool { matches!(self.response_type, ResponseType::Error(..)) } pub fn get_network_error(&self) -> Option<&NetworkError> { match self.response_type { ResponseType::Error(ref e) => Some(e), _ => None, } } pub fn actual_response(&self) -> &Response { if self.return_internal && self.internal_response.is_some() { self.internal_response.as_ref().unwrap() } else { self } } pub fn actual_response_mut(&mut self) -> &mut Response { if self.return_internal && self.internal_response.is_some() { self.internal_response.as_mut().unwrap() } else { self } } pub fn to_actual(self) -> Response { if self.return_internal && self.internal_response.is_some() { *self.internal_response.unwrap() } else { self } } pub fn get_resource_timing(&self) -> Arc> { Arc::clone(&self.resource_timing) } /// Convert to a filtered response, of type `filter_type`. /// Do not use with type Error or Default #[rustfmt::skip] pub fn to_filtered(self, filter_type: ResponseType) -> Response { match filter_type { ResponseType::Default | ResponseType::Error(..) => panic!(), _ => (), } let old_response = self.to_actual(); if let ResponseType::Error(e) = old_response.response_type { return Response::network_error(e); } let old_headers = old_response.headers.clone(); let exposed_headers = old_response.cors_exposed_header_name_list.clone(); let mut response = old_response.clone(); response.internal_response = Some(Box::new(old_response)); response.response_type = filter_type; match response.response_type { ResponseType::Default | ResponseType::Error(..) => unreachable!(), ResponseType::Basic => { let headers = old_headers.iter().filter(|(name, _)| { !matches!(&*name.as_str().to_ascii_lowercase(), "set-cookie" | "set-cookie2") }).map(|(n, v)| (n.clone(), v.clone())).collect(); response.headers = headers; }, ResponseType::Cors => { let headers = old_headers.iter().filter(|(name, _)| { match &*name.as_str().to_ascii_lowercase() { "cache-control" | "content-language" | "content-type" | "expires" | "last-modified" | "pragma" => true, "set-cookie" | "set-cookie2" => false, header => { exposed_headers.iter().any(|h| *header == h.as_str().to_ascii_lowercase()) } } }).map(|(n, v)| (n.clone(), v.clone())).collect(); response.headers = headers; }, ResponseType::Opaque => { response.url_list = vec![]; response.url = None; response.headers = HeaderMap::new(); response.status = None; response.body = Arc::new(Mutex::new(ResponseBody::Empty)); response.cache_state = CacheState::None; }, ResponseType::OpaqueRedirect => { response.headers = HeaderMap::new(); response.status = None; response.body = Arc::new(Mutex::new(ResponseBody::Empty)); response.cache_state = CacheState::None; }, } response } pub fn metadata(&self) -> Result { fn init_metadata(response: &Response, url: &ServoUrl) -> Metadata { let mut metadata = Metadata::default(url.clone()); metadata.set_content_type( response .headers .typed_get::() .map(|v| v.into()) .as_ref(), ); metadata.location_url.clone_from(&response.location_url); metadata.headers = Some(Serde(response.headers.clone())); metadata.status.clone_from(&response.raw_status); metadata.https_state = response.https_state; metadata.referrer.clone_from(&response.referrer); metadata.referrer_policy = response.referrer_policy; metadata.redirected = response.actual_response().url_list.len() > 1; metadata } if let Some(error) = self.get_network_error() { return Err(error.clone()); } let metadata = self.url.as_ref().map(|url| init_metadata(self, url)); if let Some(ref response) = self.internal_response { match response.url { Some(ref url) => { let unsafe_metadata = init_metadata(response, url); match self.response_type { ResponseType::Basic => Ok(FetchMetadata::Filtered { filtered: FilteredMetadata::Basic(metadata.unwrap()), unsafe_: unsafe_metadata, }), ResponseType::Cors => Ok(FetchMetadata::Filtered { filtered: FilteredMetadata::Cors(metadata.unwrap()), unsafe_: unsafe_metadata, }), ResponseType::Default => unreachable!(), ResponseType::Error(ref network_err) => Err(network_err.clone()), ResponseType::Opaque => Ok(FetchMetadata::Filtered { filtered: FilteredMetadata::Opaque, unsafe_: unsafe_metadata, }), ResponseType::OpaqueRedirect => Ok(FetchMetadata::Filtered { filtered: FilteredMetadata::OpaqueRedirect(url.clone()), unsafe_: unsafe_metadata, }), } }, None => Err(NetworkError::Internal( "No url found in unsafe response".to_owned(), )), } } else { assert_eq!(self.response_type, ResponseType::Default); Ok(FetchMetadata::Unfiltered(metadata.unwrap())) } } }