mirror of
https://github.com/servo/servo.git
synced 2025-07-23 07:13:52 +01:00
As discussed in https://github.com/servo/servo/issues/26807#issuecomment-640151804, we'd like to add a new flag, `in_memory_done`, to `TransmitBodyConnectHandler` so that we can correctly finish and drop the sender correctly. When we send the bytes, we will mark the body as done and we can recognize it's already done in next tick so that we can send a Done request to finish the sender. Also, when there comes a redirect request, it will go to `re-extract` route, we can set the `done` flag to `false` which means we won't stop the IPC routers yet. Then, if the re-extract sent its bytes, we will be marked as done again so that we can finish with stopping the IPC routes when Chunk request comes.
661 lines
21 KiB
Rust
661 lines
21 KiB
Rust
/* 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 crate::response::HttpsState;
|
|
use crate::ReferrerPolicy;
|
|
use crate::ResourceTimingType;
|
|
use content_security_policy::{self as csp, CspList};
|
|
use http::HeaderMap;
|
|
use hyper::Method;
|
|
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
|
|
use mime::Mime;
|
|
use msg::constellation_msg::PipelineId;
|
|
use servo_url::{ImmutableOrigin, ServoUrl};
|
|
|
|
/// 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,
|
|
/// Default referrer if nothing is specified
|
|
Client,
|
|
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, MallocSizeOf, PartialEq)]
|
|
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>
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub enum BodyChunkRequest {
|
|
/// Connect a fetch in `net`, with a stream of bytes from `script`.
|
|
Connect(IpcSender<Vec<u8>>),
|
|
/// Re-extract a new stream from the source, following a redirect.
|
|
Extract(IpcReceiver<BodyChunkRequest>),
|
|
/// Ask for another chunk.
|
|
Chunk,
|
|
/// Signal the stream is done.
|
|
Done,
|
|
}
|
|
|
|
/// 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: 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,
|
|
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 _ = self.chan.send(BodyChunkRequest::Extract(port));
|
|
self.chan = chan.clone();
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn take_stream(&mut self) -> 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: Option<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,
|
|
}
|
|
|
|
impl RequestBuilder {
|
|
pub fn new(url: ServoUrl) -> 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::Omit,
|
|
use_url_credentials: false,
|
|
origin: ImmutableOrigin::new_opaque(),
|
|
referrer: None,
|
|
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,
|
|
}
|
|
}
|
|
|
|
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(mut self, referrer: Option<Referrer>) -> RequestBuilder {
|
|
self.referrer = referrer;
|
|
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 build(self) -> Request {
|
|
let mut request = Request::new(
|
|
self.url.clone(),
|
|
Some(Origin::Origin(self.origin)),
|
|
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 = self.referrer.unwrap_or(Referrer::Client);
|
|
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
|
|
}
|
|
}
|
|
|
|
/// 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,
|
|
}
|
|
|
|
impl Request {
|
|
pub fn new(
|
|
url: ServoUrl,
|
|
origin: Option<Origin>,
|
|
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::Client,
|
|
referrer_policy: None,
|
|
pipeline_id: pipeline_id,
|
|
synchronous: false,
|
|
mode: RequestMode::NoCors,
|
|
use_cors_preflight: false,
|
|
credentials_mode: CredentialsMode::Omit,
|
|
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,
|
|
}
|
|
}
|
|
|
|
/// <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 | Referrer::Client => None,
|
|
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,
|
|
}
|
|
}
|