aboutsummaryrefslogtreecommitdiffstats
path: root/components/shared/net/response.rs
diff options
context:
space:
mode:
authorMartin Robinson <mrobinson@igalia.com>2023-10-05 19:47:39 +0200
committerMartin Robinson <mrobinson@igalia.com>2023-11-03 15:38:18 +0000
commitf4d3af296c05260dfbb3deea4f8fa400cb6887d3 (patch)
tree169db2cc68e01a755b30500dd525f1a2ec2da861 /components/shared/net/response.rs
parent863529d9622c68f0a9535225237eb5e5c5b8c757 (diff)
downloadservo-f4d3af296c05260dfbb3deea4f8fa400cb6887d3.tar.gz
servo-f4d3af296c05260dfbb3deea4f8fa400cb6887d3.zip
Move `*_traits` and other shared types to `shared`
This is the start of the organization of types that are in their own crates in order to break dependency cycles between other crates. The idea here is that putting these packages into their own directory is the first step toward cleaning them up. They have grown organically and it is difficult to explain to new folks where to put new shared types. Many of these crates contain more than traits or don't contain traits at all. Notably, `script_traits` isn't touched because it is vendored from Gecko. Eventually this will move to `third_party`.
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()))
+ }
+ }
+}