aboutsummaryrefslogtreecommitdiffstats
path: root/components/shared/net/response.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/shared/net/response.rs')
-rw-r--r--components/shared/net/response.rs364
1 files changed, 364 insertions, 0 deletions
diff --git a/components/shared/net/response.rs b/components/shared/net/response.rs
new file mode 100644
index 00000000000..552c0057c50
--- /dev/null
+++ b/components/shared/net/response.rs
@@ -0,0 +1,364 @@
+/* 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<u8>),
+ Done(Vec<u8>),
+}
+
+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<ServoUrl>,
+ pub location_url: Option<Result<ServoUrl, String>>,
+}
+
+/// 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<TerminationReason>,
+ url: Option<ServoUrl>,
+ pub url_list: Vec<ServoUrl>,
+ /// `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<u8>)>,
+ #[ignore_malloc_size_of = "Defined in hyper"]
+ pub headers: HeaderMap,
+ #[ignore_malloc_size_of = "Mutex heap size undefined"]
+ pub body: Arc<Mutex<ResponseBody>>,
+ pub cache_state: CacheState,
+ pub https_state: HttpsState,
+ pub referrer: Option<ServoUrl>,
+ pub referrer_policy: Option<ReferrerPolicy>,
+ /// [CORS-exposed header-name list](https://fetch.spec.whatwg.org/#concept-response-cors-exposed-header-name-list)
+ pub cors_exposed_header_name_list: Vec<String>,
+ /// [Location URL](https://fetch.spec.whatwg.org/#concept-response-location-url)
+ pub location_url: Option<Result<ServoUrl, String>>,
+ /// [Internal response](https://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response
+ /// is a filtered response
+ pub internal_response: Option<Box<Response>>,
+ /// whether or not to try to return the internal_response when asked for actual_response
+ pub return_internal: bool,
+ /// https://fetch.spec.whatwg.org/#concept-response-aborted
+ #[ignore_malloc_size_of = "AtomicBool heap size undefined"]
+ pub aborted: Arc<AtomicBool>,
+ /// track network metrics
+ #[ignore_malloc_size_of = "Mutex heap size undefined"]
+ pub resource_timing: Arc<Mutex<ResourceFetchTiming>>,
+}
+
+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 {
+ match self.response_type {
+ ResponseType::Error(..) => true,
+ _ => false,
+ }
+ }
+
+ 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() {
+ &mut **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<Mutex<ResourceFetchTiming>> {
+ 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, _)| {
+ match &*name.as_str().to_ascii_lowercase() {
+ "set-cookie" | "set-cookie2" => false,
+ _ => true
+ }
+ }).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<FetchMetadata, NetworkError> {
+ fn init_metadata(response: &Response, url: &ServoUrl) -> Metadata {
+ let mut metadata = Metadata::default(url.clone());
+ metadata.set_content_type(
+ response
+ .headers
+ .typed_get::<ContentType>()
+ .map(|v| v.into())
+ .as_ref(),
+ );
+ metadata.location_url = response.location_url.clone();
+ metadata.headers = Some(Serde(response.headers.clone()));
+ metadata.status = response.raw_status.clone();
+ metadata.https_state = response.https_state;
+ metadata.referrer = response.referrer.clone();
+ metadata.referrer_policy = response.referrer_policy.clone();
+ 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()))
+ }
+ }
+}