aboutsummaryrefslogtreecommitdiffstats
path: root/components/shared/net/request.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/shared/net/request.rs')
-rw-r--r--components/shared/net/request.rs749
1 files changed, 749 insertions, 0 deletions
diff --git a/components/shared/net/request.rs b/components/shared/net/request.rs
new file mode 100644
index 00000000000..1f9f0abf986
--- /dev/null
+++ b/components/shared/net/request.rs
@@ -0,0 +1,749 @@
+/* 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/. */
+
+use std::sync::{Arc, Mutex};
+
+use content_security_policy::{self as csp, CspList};
+use http::header::{HeaderName, AUTHORIZATION};
+use http::{HeaderMap, Method};
+use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
+use malloc_size_of_derive::MallocSizeOf;
+use mime::Mime;
+use msg::constellation_msg::PipelineId;
+use serde::{Deserialize, Serialize};
+use servo_url::{ImmutableOrigin, ServoUrl};
+
+use crate::response::HttpsState;
+use crate::{ReferrerPolicy, ResourceTimingType};
+
+/// An [initiator](https://fetch.spec.whatwg.org/#concept-request-initiator)
+#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
+pub enum Initiator {
+ None,
+ Download,
+ ImageSet,
+ Manifest,
+ XSLT,
+}
+
+/// A request [destination](https://fetch.spec.whatwg.org/#concept-request-destination)
+pub use csp::Destination;
+
+/// A request [origin](https://fetch.spec.whatwg.org/#concept-request-origin)
+#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
+pub enum Origin {
+ Client,
+ Origin(ImmutableOrigin),
+}
+
+/// A [referer](https://fetch.spec.whatwg.org/#concept-request-referrer)
+#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
+pub enum Referrer {
+ NoReferrer,
+ /// Contains the url that "client" would be resolved to. See
+ /// [https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer](https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer)
+ ///
+ /// If you are unsure you should probably use
+ /// [`GlobalScope::get_referrer`](https://doc.servo.org/script/dom/globalscope/struct.GlobalScope.html#method.get_referrer)
+ Client(ServoUrl),
+ ReferrerUrl(ServoUrl),
+}
+
+/// A [request mode](https://fetch.spec.whatwg.org/#concept-request-mode)
+#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
+pub enum RequestMode {
+ Navigate,
+ SameOrigin,
+ NoCors,
+ CorsMode,
+ WebSocket { protocols: Vec<String> },
+}
+
+/// Request [credentials mode](https://fetch.spec.whatwg.org/#concept-request-credentials-mode)
+#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
+pub enum CredentialsMode {
+ Omit,
+ CredentialsSameOrigin,
+ Include,
+}
+
+/// [Cache mode](https://fetch.spec.whatwg.org/#concept-request-cache-mode)
+#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
+pub enum CacheMode {
+ Default,
+ NoStore,
+ Reload,
+ NoCache,
+ ForceCache,
+ OnlyIfCached,
+}
+
+/// [Service-workers mode](https://fetch.spec.whatwg.org/#request-service-workers-mode)
+#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
+pub enum ServiceWorkersMode {
+ All,
+ None,
+}
+
+/// [Redirect mode](https://fetch.spec.whatwg.org/#concept-request-redirect-mode)
+#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
+pub enum RedirectMode {
+ Follow,
+ Error,
+ Manual,
+}
+
+/// [Response tainting](https://fetch.spec.whatwg.org/#concept-request-response-tainting)
+#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
+pub enum ResponseTainting {
+ Basic,
+ CorsTainting,
+ Opaque,
+}
+
+/// [Window](https://fetch.spec.whatwg.org/#concept-request-window)
+#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
+pub enum Window {
+ NoWindow,
+ Client, // TODO: Environmental settings object
+}
+
+/// [CORS settings attribute](https://html.spec.whatwg.org/multipage/#attr-crossorigin-anonymous)
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub enum CorsSettings {
+ Anonymous,
+ UseCredentials,
+}
+
+/// [Parser Metadata](https://fetch.spec.whatwg.org/#concept-request-parser-metadata)
+#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
+pub enum ParserMetadata {
+ Default,
+ ParserInserted,
+ NotParserInserted,
+}
+
+/// <https://fetch.spec.whatwg.org/#concept-body-source>
+#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
+pub enum BodySource {
+ Null,
+ Object,
+}
+
+/// Messages used to implement <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
+/// which are sent from script to net.
+#[derive(Debug, Deserialize, Serialize)]
+pub enum BodyChunkResponse {
+ /// A chunk of bytes.
+ Chunk(Vec<u8>),
+ /// The body is done.
+ Done,
+ /// There was an error streaming the body,
+ /// terminate fetch.
+ Error,
+}
+
+/// Messages used to implement <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
+/// which are sent from net to script
+/// (with the exception of Done, which is sent from script to script).
+#[derive(Debug, Deserialize, Serialize)]
+pub enum BodyChunkRequest {
+ /// Connect a fetch in `net`, with a stream of bytes from `script`.
+ Connect(IpcSender<BodyChunkResponse>),
+ /// Re-extract a new stream from the source, following a redirect.
+ Extract(IpcReceiver<BodyChunkRequest>),
+ /// Ask for another chunk.
+ Chunk,
+ /// Signal the stream is done(sent from script to script).
+ Done,
+ /// Signal the stream has errored(sent from script to script).
+ Error,
+}
+
+/// The net component's view into <https://fetch.spec.whatwg.org/#bodies>
+#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
+pub struct RequestBody {
+ /// Net's channel to communicate with script re this body.
+ #[ignore_malloc_size_of = "Channels are hard"]
+ chan: Arc<Mutex<IpcSender<BodyChunkRequest>>>,
+ /// <https://fetch.spec.whatwg.org/#concept-body-source>
+ source: BodySource,
+ /// <https://fetch.spec.whatwg.org/#concept-body-total-bytes>
+ total_bytes: Option<usize>,
+}
+
+impl RequestBody {
+ pub fn new(
+ chan: IpcSender<BodyChunkRequest>,
+ source: BodySource,
+ total_bytes: Option<usize>,
+ ) -> Self {
+ RequestBody {
+ chan: Arc::new(Mutex::new(chan)),
+ source,
+ total_bytes,
+ }
+ }
+
+ /// Step 12 of https://fetch.spec.whatwg.org/#concept-http-redirect-fetch
+ pub fn extract_source(&mut self) {
+ match self.source {
+ BodySource::Null => panic!("Null sources should never be re-directed."),
+ BodySource::Object => {
+ let (chan, port) = ipc::channel().unwrap();
+ let mut selfchan = self.chan.lock().unwrap();
+ let _ = selfchan.send(BodyChunkRequest::Extract(port));
+ *selfchan = chan;
+ },
+ }
+ }
+
+ pub fn take_stream(&self) -> Arc<Mutex<IpcSender<BodyChunkRequest>>> {
+ self.chan.clone()
+ }
+
+ pub fn source_is_null(&self) -> bool {
+ self.source == BodySource::Null
+ }
+
+ pub fn len(&self) -> Option<usize> {
+ self.total_bytes.clone()
+ }
+}
+
+#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
+pub struct RequestBuilder {
+ #[serde(
+ deserialize_with = "::hyper_serde::deserialize",
+ serialize_with = "::hyper_serde::serialize"
+ )]
+ #[ignore_malloc_size_of = "Defined in hyper"]
+ pub method: Method,
+ 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 unsafe_request: bool,
+ pub body: Option<RequestBody>,
+ pub service_workers_mode: ServiceWorkersMode,
+ // TODO: client object
+ pub destination: Destination,
+ pub synchronous: bool,
+ pub mode: RequestMode,
+ pub cache_mode: CacheMode,
+ pub use_cors_preflight: bool,
+ pub credentials_mode: CredentialsMode,
+ pub use_url_credentials: bool,
+ pub origin: ImmutableOrigin,
+ // XXXManishearth these should be part of the client object
+ pub referrer: Referrer,
+ pub referrer_policy: Option<ReferrerPolicy>,
+ pub pipeline_id: Option<PipelineId>,
+ pub redirect_mode: RedirectMode,
+ pub integrity_metadata: String,
+ // This is nominally a part of the client's global object.
+ // It is copied here to avoid having to reach across the thread
+ // boundary every time a redirect occurs.
+ #[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
+ pub csp_list: Option<CspList>,
+ // to keep track of redirects
+ pub url_list: Vec<ServoUrl>,
+ pub parser_metadata: ParserMetadata,
+ pub initiator: Initiator,
+ pub https_state: HttpsState,
+ pub response_tainting: ResponseTainting,
+ /// Servo internal: if crash details are present, trigger a crash error page with these details.
+ pub crash: Option<String>,
+}
+
+impl RequestBuilder {
+ pub fn new(url: ServoUrl, referrer: Referrer) -> RequestBuilder {
+ RequestBuilder {
+ method: Method::GET,
+ url: url,
+ headers: HeaderMap::new(),
+ unsafe_request: false,
+ body: None,
+ service_workers_mode: ServiceWorkersMode::All,
+ destination: Destination::None,
+ synchronous: false,
+ mode: RequestMode::NoCors,
+ cache_mode: CacheMode::Default,
+ use_cors_preflight: false,
+ credentials_mode: CredentialsMode::CredentialsSameOrigin,
+ use_url_credentials: false,
+ origin: ImmutableOrigin::new_opaque(),
+ referrer: referrer,
+ referrer_policy: None,
+ pipeline_id: None,
+ redirect_mode: RedirectMode::Follow,
+ integrity_metadata: "".to_owned(),
+ url_list: vec![],
+ parser_metadata: ParserMetadata::Default,
+ initiator: Initiator::None,
+ csp_list: None,
+ https_state: HttpsState::None,
+ response_tainting: ResponseTainting::Basic,
+ crash: None,
+ }
+ }
+
+ pub fn initiator(mut self, initiator: Initiator) -> RequestBuilder {
+ self.initiator = initiator;
+ self
+ }
+
+ pub fn method(mut self, method: Method) -> RequestBuilder {
+ self.method = method;
+ self
+ }
+
+ pub fn headers(mut self, headers: HeaderMap) -> RequestBuilder {
+ self.headers = headers;
+ self
+ }
+
+ pub fn unsafe_request(mut self, unsafe_request: bool) -> RequestBuilder {
+ self.unsafe_request = unsafe_request;
+ self
+ }
+
+ pub fn body(mut self, body: Option<RequestBody>) -> RequestBuilder {
+ self.body = body;
+ self
+ }
+
+ pub fn destination(mut self, destination: Destination) -> RequestBuilder {
+ self.destination = destination;
+ self
+ }
+
+ pub fn synchronous(mut self, synchronous: bool) -> RequestBuilder {
+ self.synchronous = synchronous;
+ self
+ }
+
+ pub fn mode(mut self, mode: RequestMode) -> RequestBuilder {
+ self.mode = mode;
+ self
+ }
+
+ pub fn use_cors_preflight(mut self, use_cors_preflight: bool) -> RequestBuilder {
+ self.use_cors_preflight = use_cors_preflight;
+ self
+ }
+
+ pub fn credentials_mode(mut self, credentials_mode: CredentialsMode) -> RequestBuilder {
+ self.credentials_mode = credentials_mode;
+ self
+ }
+
+ pub fn use_url_credentials(mut self, use_url_credentials: bool) -> RequestBuilder {
+ self.use_url_credentials = use_url_credentials;
+ self
+ }
+
+ pub fn origin(mut self, origin: ImmutableOrigin) -> RequestBuilder {
+ self.origin = origin;
+ self
+ }
+
+ pub fn referrer_policy(mut self, referrer_policy: Option<ReferrerPolicy>) -> RequestBuilder {
+ self.referrer_policy = referrer_policy;
+ self
+ }
+
+ pub fn pipeline_id(mut self, pipeline_id: Option<PipelineId>) -> RequestBuilder {
+ self.pipeline_id = pipeline_id;
+ self
+ }
+
+ pub fn redirect_mode(mut self, redirect_mode: RedirectMode) -> RequestBuilder {
+ self.redirect_mode = redirect_mode;
+ self
+ }
+
+ pub fn integrity_metadata(mut self, integrity_metadata: String) -> RequestBuilder {
+ self.integrity_metadata = integrity_metadata;
+ self
+ }
+
+ pub fn parser_metadata(mut self, parser_metadata: ParserMetadata) -> RequestBuilder {
+ self.parser_metadata = parser_metadata;
+ self
+ }
+
+ pub fn https_state(mut self, https_state: HttpsState) -> RequestBuilder {
+ self.https_state = https_state;
+ self
+ }
+
+ pub fn response_tainting(mut self, response_tainting: ResponseTainting) -> RequestBuilder {
+ self.response_tainting = response_tainting;
+ self
+ }
+
+ pub fn crash(mut self, crash: Option<String>) -> Self {
+ self.crash = crash;
+ self
+ }
+
+ pub fn build(self) -> Request {
+ let mut request = Request::new(
+ self.url.clone(),
+ Some(Origin::Origin(self.origin)),
+ self.referrer,
+ self.pipeline_id,
+ self.https_state,
+ );
+ request.initiator = self.initiator;
+ request.method = self.method;
+ request.headers = self.headers;
+ request.unsafe_request = self.unsafe_request;
+ request.body = self.body;
+ request.service_workers_mode = self.service_workers_mode;
+ request.destination = self.destination;
+ request.synchronous = self.synchronous;
+ request.mode = self.mode;
+ request.use_cors_preflight = self.use_cors_preflight;
+ request.credentials_mode = self.credentials_mode;
+ request.use_url_credentials = self.use_url_credentials;
+ request.cache_mode = self.cache_mode;
+ request.referrer_policy = self.referrer_policy;
+ request.redirect_mode = self.redirect_mode;
+ let mut url_list = self.url_list;
+ if url_list.is_empty() {
+ url_list.push(self.url);
+ }
+ request.redirect_count = url_list.len() as u32 - 1;
+ request.url_list = url_list;
+ request.integrity_metadata = self.integrity_metadata;
+ request.parser_metadata = self.parser_metadata;
+ request.csp_list = self.csp_list;
+ request.response_tainting = self.response_tainting;
+ request.crash = self.crash;
+ request
+ }
+}
+
+/// A [Request](https://fetch.spec.whatwg.org/#concept-request) as defined by
+/// the Fetch spec.
+#[derive(Clone, MallocSizeOf)]
+pub struct Request {
+ /// <https://fetch.spec.whatwg.org/#concept-request-method>
+ #[ignore_malloc_size_of = "Defined in hyper"]
+ pub method: Method,
+ /// <https://fetch.spec.whatwg.org/#local-urls-only-flag>
+ pub local_urls_only: bool,
+ /// <https://fetch.spec.whatwg.org/#sandboxed-storage-area-urls-flag>
+ pub sandboxed_storage_area_urls: bool,
+ /// <https://fetch.spec.whatwg.org/#concept-request-header-list>
+ #[ignore_malloc_size_of = "Defined in hyper"]
+ pub headers: HeaderMap,
+ /// <https://fetch.spec.whatwg.org/#unsafe-request-flag>
+ pub unsafe_request: bool,
+ /// <https://fetch.spec.whatwg.org/#concept-request-body>
+ pub body: Option<RequestBody>,
+ // TODO: client object
+ pub window: Window,
+ // TODO: target browsing context
+ /// <https://fetch.spec.whatwg.org/#request-keepalive-flag>
+ pub keep_alive: bool,
+ /// <https://fetch.spec.whatwg.org/#request-service-workers-mode>
+ pub service_workers_mode: ServiceWorkersMode,
+ /// <https://fetch.spec.whatwg.org/#concept-request-initiator>
+ pub initiator: Initiator,
+ /// <https://fetch.spec.whatwg.org/#concept-request-destination>
+ pub destination: Destination,
+ // TODO: priority object
+ /// <https://fetch.spec.whatwg.org/#concept-request-origin>
+ pub origin: Origin,
+ /// <https://fetch.spec.whatwg.org/#concept-request-referrer>
+ pub referrer: Referrer,
+ /// <https://fetch.spec.whatwg.org/#concept-request-referrer-policy>
+ pub referrer_policy: Option<ReferrerPolicy>,
+ pub pipeline_id: Option<PipelineId>,
+ /// <https://fetch.spec.whatwg.org/#synchronous-flag>
+ pub synchronous: bool,
+ /// <https://fetch.spec.whatwg.org/#concept-request-mode>
+ pub mode: RequestMode,
+ /// <https://fetch.spec.whatwg.org/#use-cors-preflight-flag>
+ pub use_cors_preflight: bool,
+ /// <https://fetch.spec.whatwg.org/#concept-request-credentials-mode>
+ pub credentials_mode: CredentialsMode,
+ /// <https://fetch.spec.whatwg.org/#concept-request-use-url-credentials-flag>
+ pub use_url_credentials: bool,
+ /// <https://fetch.spec.whatwg.org/#concept-request-cache-mode>
+ pub cache_mode: CacheMode,
+ /// <https://fetch.spec.whatwg.org/#concept-request-redirect-mode>
+ pub redirect_mode: RedirectMode,
+ /// <https://fetch.spec.whatwg.org/#concept-request-integrity-metadata>
+ pub integrity_metadata: String,
+ // Use the last method on url_list to act as spec current url field, and
+ // first method to act as spec url field
+ /// <https://fetch.spec.whatwg.org/#concept-request-url-list>
+ pub url_list: Vec<ServoUrl>,
+ /// <https://fetch.spec.whatwg.org/#concept-request-redirect-count>
+ pub redirect_count: u32,
+ /// <https://fetch.spec.whatwg.org/#concept-request-response-tainting>
+ pub response_tainting: ResponseTainting,
+ /// <https://fetch.spec.whatwg.org/#concept-request-parser-metadata>
+ pub parser_metadata: ParserMetadata,
+ // This is nominally a part of the client's global object.
+ // It is copied here to avoid having to reach across the thread
+ // boundary every time a redirect occurs.
+ #[ignore_malloc_size_of = "Defined in rust-content-security-policy"]
+ pub csp_list: Option<CspList>,
+ pub https_state: HttpsState,
+ /// Servo internal: if crash details are present, trigger a crash error page with these details.
+ pub crash: Option<String>,
+}
+
+impl Request {
+ pub fn new(
+ url: ServoUrl,
+ origin: Option<Origin>,
+ referrer: Referrer,
+ pipeline_id: Option<PipelineId>,
+ https_state: HttpsState,
+ ) -> Request {
+ Request {
+ method: Method::GET,
+ local_urls_only: false,
+ sandboxed_storage_area_urls: false,
+ headers: HeaderMap::new(),
+ unsafe_request: false,
+ body: None,
+ window: Window::Client,
+ keep_alive: false,
+ service_workers_mode: ServiceWorkersMode::All,
+ initiator: Initiator::None,
+ destination: Destination::None,
+ origin: origin.unwrap_or(Origin::Client),
+ referrer: referrer,
+ referrer_policy: None,
+ pipeline_id: pipeline_id,
+ synchronous: false,
+ mode: RequestMode::NoCors,
+ use_cors_preflight: false,
+ credentials_mode: CredentialsMode::CredentialsSameOrigin,
+ use_url_credentials: false,
+ cache_mode: CacheMode::Default,
+ redirect_mode: RedirectMode::Follow,
+ integrity_metadata: String::new(),
+ url_list: vec![url],
+ parser_metadata: ParserMetadata::Default,
+ redirect_count: 0,
+ response_tainting: ResponseTainting::Basic,
+ csp_list: None,
+ https_state: https_state,
+ crash: None,
+ }
+ }
+
+ /// <https://fetch.spec.whatwg.org/#concept-request-url>
+ pub fn url(&self) -> ServoUrl {
+ self.url_list.first().unwrap().clone()
+ }
+
+ /// <https://fetch.spec.whatwg.org/#concept-request-current-url>
+ pub fn current_url(&self) -> ServoUrl {
+ self.url_list.last().unwrap().clone()
+ }
+
+ /// <https://fetch.spec.whatwg.org/#concept-request-current-url>
+ pub fn current_url_mut(&mut self) -> &mut ServoUrl {
+ self.url_list.last_mut().unwrap()
+ }
+
+ /// <https://fetch.spec.whatwg.org/#navigation-request>
+ pub fn is_navigation_request(&self) -> bool {
+ self.destination == Destination::Document
+ }
+
+ /// <https://fetch.spec.whatwg.org/#subresource-request>
+ pub fn is_subresource_request(&self) -> bool {
+ match self.destination {
+ Destination::Audio |
+ Destination::Font |
+ Destination::Image |
+ Destination::Manifest |
+ Destination::Script |
+ Destination::Style |
+ Destination::Track |
+ Destination::Video |
+ Destination::Xslt |
+ Destination::None => true,
+ _ => false,
+ }
+ }
+
+ pub fn timing_type(&self) -> ResourceTimingType {
+ if self.is_navigation_request() {
+ ResourceTimingType::Navigation
+ } else {
+ ResourceTimingType::Resource
+ }
+ }
+}
+
+impl Referrer {
+ pub fn to_url(&self) -> Option<&ServoUrl> {
+ match *self {
+ Referrer::NoReferrer => None,
+ Referrer::Client(ref url) => Some(url),
+ Referrer::ReferrerUrl(ref url) => Some(url),
+ }
+ }
+}
+
+// https://fetch.spec.whatwg.org/#cors-unsafe-request-header-byte
+// TODO: values in the control-code range are being quietly stripped out by
+// HeaderMap and never reach this function to be loudly rejected!
+fn is_cors_unsafe_request_header_byte(value: &u8) -> bool {
+ match value {
+ 0x00..=0x08 |
+ 0x10..=0x19 |
+ 0x22 |
+ 0x28 |
+ 0x29 |
+ 0x3A |
+ 0x3C |
+ 0x3E |
+ 0x3F |
+ 0x40 |
+ 0x5B |
+ 0x5C |
+ 0x5D |
+ 0x7B |
+ 0x7D |
+ 0x7F => true,
+ _ => false,
+ }
+}
+
+// https://fetch.spec.whatwg.org/#cors-safelisted-request-header
+// subclause `accept`
+fn is_cors_safelisted_request_accept(value: &[u8]) -> bool {
+ !(value.iter().any(is_cors_unsafe_request_header_byte))
+}
+
+// https://fetch.spec.whatwg.org/#cors-safelisted-request-header
+// subclauses `accept-language`, `content-language`
+fn is_cors_safelisted_language(value: &[u8]) -> bool {
+ value.iter().all(|&x| match x {
+ 0x30..=0x39 |
+ 0x41..=0x5A |
+ 0x61..=0x7A |
+ 0x20 |
+ 0x2A |
+ 0x2C |
+ 0x2D |
+ 0x2E |
+ 0x3B |
+ 0x3D => true,
+ _ => false,
+ })
+}
+
+// https://fetch.spec.whatwg.org/#cors-safelisted-request-header
+// subclause `content-type`
+fn is_cors_safelisted_request_content_type(value: &[u8]) -> bool {
+ // step 1
+ if value.iter().any(is_cors_unsafe_request_header_byte) {
+ return false;
+ }
+ // step 2
+ let value_string = if let Ok(s) = std::str::from_utf8(value) {
+ s
+ } else {
+ return false;
+ };
+ let value_mime_result: Result<Mime, _> = value_string.parse();
+ match value_mime_result {
+ Err(_) => false, // step 3
+ Ok(value_mime) => match (value_mime.type_(), value_mime.subtype()) {
+ (mime::APPLICATION, mime::WWW_FORM_URLENCODED) |
+ (mime::MULTIPART, mime::FORM_DATA) |
+ (mime::TEXT, mime::PLAIN) => true,
+ _ => false, // step 4
+ },
+ }
+}
+
+// TODO: "DPR", "Downlink", "Save-Data", "Viewport-Width", "Width":
+// ... once parsed, the value should not be failure.
+// https://fetch.spec.whatwg.org/#cors-safelisted-request-header
+pub fn is_cors_safelisted_request_header<N: AsRef<str>, V: AsRef<[u8]>>(
+ name: &N,
+ value: &V,
+) -> bool {
+ let name: &str = name.as_ref();
+ let value: &[u8] = value.as_ref();
+ if value.len() > 128 {
+ return false;
+ }
+ match name {
+ "accept" => is_cors_safelisted_request_accept(value),
+ "accept-language" | "content-language" => is_cors_safelisted_language(value),
+ "content-type" => is_cors_safelisted_request_content_type(value),
+ _ => false,
+ }
+}
+
+/// <https://fetch.spec.whatwg.org/#cors-safelisted-method>
+pub fn is_cors_safelisted_method(m: &Method) -> bool {
+ match *m {
+ Method::GET | Method::HEAD | Method::POST => true,
+ _ => false,
+ }
+}
+
+/// <https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name>
+pub fn is_cors_non_wildcard_request_header_name(name: &HeaderName) -> bool {
+ name == AUTHORIZATION
+}
+
+/// <https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names>
+pub fn get_cors_unsafe_header_names(headers: &HeaderMap) -> Vec<HeaderName> {
+ // Step 1
+ let mut unsafe_names: Vec<&HeaderName> = vec![];
+ // Step 2
+ let mut potentillay_unsafe_names: Vec<&HeaderName> = vec![];
+ // Step 3
+ let mut safelist_value_size = 0;
+
+ // Step 4
+ for (name, value) in headers.iter() {
+ if !is_cors_safelisted_request_header(&name, &value) {
+ unsafe_names.push(name);
+ } else {
+ potentillay_unsafe_names.push(name);
+ safelist_value_size += value.as_ref().len();
+ }
+ }
+
+ // Step 5
+ if safelist_value_size > 1024 {
+ unsafe_names.extend_from_slice(&potentillay_unsafe_names);
+ }
+
+ // Step 6
+ return convert_header_names_to_sorted_lowercase_set(unsafe_names);
+}
+
+/// <https://fetch.spec.whatwg.org/#ref-for-convert-header-names-to-a-sorted-lowercase-set>
+pub fn convert_header_names_to_sorted_lowercase_set(
+ header_names: Vec<&HeaderName>,
+) -> Vec<HeaderName> {
+ // HeaderName does not implement the needed traits to use a BTreeSet
+ // So create a new Vec, sort, then dedup
+ let mut ordered_set = header_names.to_vec();
+ ordered_set.sort_by(|a, b| a.as_str().partial_cmp(b.as_str()).unwrap());
+ ordered_set.dedup();
+ return ordered_set.into_iter().cloned().collect();
+}