use std::sync::Arc;
use content_security_policy::{self as csp};
use http::header::{AUTHORIZATION, HeaderName};
use http::{HeaderMap, Method};
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use ipc_channel::router::ROUTER;
use log::error;
use malloc_size_of_derive::MallocSizeOf;
use mime::Mime;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use servo_base::generic_channel::GenericSharedMemory;
use servo_base::id::{PipelineId, WebViewId};
use servo_url::{ImmutableOrigin, ServoUrl};
use tokio::sync::oneshot::Sender as TokioSender;
use url::Position;
use uuid::Uuid;
use crate::policy_container::{PolicyContainer, RequestPolicyContainer};
use crate::pub_domains::is_same_site;
use crate::response::{HttpsState, RedirectTaint, Response};
use crate::{ReferrerPolicy, ResourceTimingType};
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct RequestId(pub Uuid);
impl Default for RequestId {
fn default() -> Self {
Self(Uuid::new_v4())
}
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum Initiator {
None,
Download,
ImageSet,
Manifest,
XSLT,
Prefetch,
Link,
}
pub use csp::Destination;
#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum Origin {
Client,
Origin(ImmutableOrigin),
}
impl Origin {
pub fn is_opaque(&self) -> bool {
matches!(self, Origin::Origin(ImmutableOrigin::Opaque(_)))
}
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum Referrer {
NoReferrer,
Client(ServoUrl),
ReferrerUrl(ServoUrl),
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub enum RequestMode {
Navigate,
SameOrigin,
NoCors,
CorsMode,
WebSocket {
protocols: Vec<String>,
original_url: ServoUrl,
},
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub enum CredentialsMode {
Omit,
CredentialsSameOrigin,
Include,
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum CacheMode {
Default,
NoStore,
Reload,
NoCache,
ForceCache,
OnlyIfCached,
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum ServiceWorkersMode {
All,
None,
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum RedirectMode {
Follow,
Error,
Manual,
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum ResponseTainting {
Basic,
CorsTainting,
Opaque,
}
#[derive(Clone, Debug, Eq, Hash, Deserialize, MallocSizeOf, Serialize, PartialEq)]
pub struct PreloadKey {
pub url: ServoUrl,
pub destination: Destination,
pub mode: RequestMode,
pub credentials_mode: CredentialsMode,
}
impl PreloadKey {
pub fn new(request: &RequestBuilder) -> Self {
Self {
url: request.url.clone(),
destination: request.destination,
mode: request.mode.clone(),
credentials_mode: request.credentials_mode,
}
}
}
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash, MallocSizeOf)]
pub struct PreloadId(pub Uuid);
impl Default for PreloadId {
fn default() -> Self {
Self(Uuid::new_v4())
}
}
#[derive(Debug, MallocSizeOf)]
pub struct PreloadEntry {
pub integrity_metadata: String,
pub response: Option<Response>,
#[ignore_malloc_size_of = "Channels are hard"]
pub on_response_available: Option<TokioSender<Response>>,
}
impl PreloadEntry {
pub fn new(integrity_metadata: String) -> Self {
Self {
integrity_metadata,
response: None,
on_response_available: None,
}
}
pub fn with_response(&mut self, response: Response) {
if let Some(sender) = self.on_response_available.take() {
let _ = sender.send(response);
} else {
self.response = Some(response);
}
}
}
pub type PreloadedResources = FxHashMap<PreloadKey, PreloadId>;
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct RequestClient {
pub preloaded_resources: PreloadedResources,
pub policy_container: RequestPolicyContainer,
pub origin: Origin,
pub is_nested_browsing_context: bool,
pub insecure_requests_policy: InsecureRequestsPolicy,
}
#[derive(Clone, Copy, Default, MallocSizeOf, PartialEq)]
pub enum SystemVisibilityState {
#[default]
Hidden,
Visible,
}
#[derive(Clone, Copy, Default, MallocSizeOf, PartialEq)]
pub struct TraversableNavigable {
current_session_history_step: u8,
running_nested_apply_history_step: bool,
system_visibility_state: SystemVisibilityState,
is_created_by_web_content: bool,
}
#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
pub enum TraversableForUserPrompts {
NoTraversable,
Client,
TraversableNavigable(TraversableNavigable),
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub enum CorsSettings {
Anonymous,
UseCredentials,
}
impl CorsSettings {
pub fn from_enumerated_attribute(value: &str) -> CorsSettings {
match value.to_ascii_lowercase().as_str() {
"anonymous" => CorsSettings::Anonymous,
"use-credentials" => CorsSettings::UseCredentials,
_ => CorsSettings::Anonymous,
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum ParserMetadata {
Default,
ParserInserted,
NotParserInserted,
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum BodySource {
Null,
Object,
}
#[derive(Debug, Deserialize, Serialize)]
pub enum BodyChunkResponse {
Chunk(GenericSharedMemory),
Done,
Error,
}
#[derive(Debug, Deserialize, Serialize)]
pub enum BodyChunkRequest {
Connect(IpcSender<BodyChunkResponse>),
Extract(IpcReceiver<BodyChunkRequest>),
Chunk,
Done,
Error,
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct RequestBody {
#[ignore_malloc_size_of = "Channels are hard"]
body_chunk_request_channel: Arc<Mutex<Option<IpcSender<BodyChunkRequest>>>>,
source: BodySource,
total_bytes: Option<usize>,
}
impl RequestBody {
pub fn new(
body_chunk_request_channel: IpcSender<BodyChunkRequest>,
source: BodySource,
total_bytes: Option<usize>,
) -> Self {
RequestBody {
body_chunk_request_channel: Arc::new(Mutex::new(Some(body_chunk_request_channel))),
source,
total_bytes,
}
}
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 lock = self.body_chunk_request_channel.lock();
let Some(selfchan) = lock.as_mut() else {
error!(
"Could not re-extract the request body source because the body stream has already been closed."
);
return;
};
if let Err(error) = selfchan.send(BodyChunkRequest::Extract(port)) {
error!(
"Could not re-extract the request body source because the body stream has already been closed: {error}"
);
return;
}
*selfchan = chan;
},
}
}
pub fn clone_stream(&self) -> Arc<Mutex<Option<IpcSender<BodyChunkRequest>>>> {
self.body_chunk_request_channel.clone()
}
pub fn close_stream(&self) {
self.body_chunk_request_channel.lock().take();
}
pub fn source_is_null(&self) -> bool {
self.source == BodySource::Null
}
#[expect(clippy::len_without_is_empty)]
pub fn len(&self) -> Option<usize> {
self.total_bytes
}
}
trait RequestBodySize {
fn body_length(&self) -> usize;
}
impl RequestBodySize for Option<RequestBody> {
fn body_length(&self) -> usize {
self.as_ref()
.and_then(|body| body.len())
.unwrap_or_default()
}
}
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum InsecureRequestsPolicy {
DoNotUpgrade,
Upgrade,
}
pub trait RequestHeadersSize {
fn total_size(&self) -> usize;
}
impl RequestHeadersSize for HeaderMap {
fn total_size(&self) -> usize {
self.iter()
.map(|(name, value)| name.as_str().len() + value.len())
.sum()
}
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct RequestBuilder {
pub id: RequestId,
pub preload_id: Option<PreloadId>,
#[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,
pub client: Option<RequestClient>,
pub destination: Destination,
pub synchronous: bool,
pub mode: RequestMode,
pub cache_mode: CacheMode,
pub use_cors_preflight: bool,
pub keep_alive: bool,
pub credentials_mode: CredentialsMode,
pub use_url_credentials: bool,
pub origin: Origin,
pub policy_container: RequestPolicyContainer,
pub insecure_requests_policy: InsecureRequestsPolicy,
pub has_trustworthy_ancestor_origin: bool,
pub referrer: Referrer,
pub referrer_policy: ReferrerPolicy,
pub pipeline_id: Option<PipelineId>,
pub target_webview_id: Option<WebViewId>,
pub redirect_mode: RedirectMode,
pub integrity_metadata: String,
pub cryptographic_nonce_metadata: String,
pub url_list: Vec<ServoUrl>,
pub parser_metadata: ParserMetadata,
pub initiator: Initiator,
pub https_state: HttpsState,
pub response_tainting: ResponseTainting,
pub crash: Option<String>,
}
impl RequestBuilder {
pub fn new(webview_id: Option<WebViewId>, url: ServoUrl, referrer: Referrer) -> RequestBuilder {
RequestBuilder {
id: RequestId::default(),
preload_id: None,
method: Method::GET,
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,
keep_alive: false,
credentials_mode: CredentialsMode::CredentialsSameOrigin,
use_url_credentials: false,
origin: Origin::Client,
client: None,
policy_container: RequestPolicyContainer::default(),
insecure_requests_policy: InsecureRequestsPolicy::DoNotUpgrade,
has_trustworthy_ancestor_origin: false,
referrer,
referrer_policy: ReferrerPolicy::EmptyString,
pipeline_id: None,
target_webview_id: webview_id,
redirect_mode: RedirectMode::Follow,
integrity_metadata: "".to_owned(),
cryptographic_nonce_metadata: "".to_owned(),
url_list: vec![],
parser_metadata: ParserMetadata::Default,
initiator: Initiator::None,
https_state: HttpsState::None,
response_tainting: ResponseTainting::Basic,
crash: None,
}
}
pub fn preload_id(mut self, preload_id: PreloadId) -> RequestBuilder {
self.preload_id = Some(preload_id);
self
}
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 keep_alive(mut self, keep_alive: bool) -> RequestBuilder {
self.keep_alive = keep_alive;
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::Origin(origin);
self
}
pub fn referrer_policy(mut self, referrer_policy: 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 cryptographic_nonce_metadata(mut self, nonce_metadata: String) -> RequestBuilder {
self.cryptographic_nonce_metadata = nonce_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 policy_container(mut self, policy_container: PolicyContainer) -> RequestBuilder {
self.policy_container = RequestPolicyContainer::PolicyContainer(policy_container);
self
}
pub fn client(mut self, client: RequestClient) -> RequestBuilder {
self.client = Some(client);
self
}
pub fn insecure_requests_policy(
mut self,
insecure_requests_policy: InsecureRequestsPolicy,
) -> RequestBuilder {
self.insecure_requests_policy = insecure_requests_policy;
self
}
pub fn has_trustworthy_ancestor_origin(
mut self,
has_trustworthy_ancestor_origin: bool,
) -> RequestBuilder {
self.has_trustworthy_ancestor_origin = has_trustworthy_ancestor_origin;
self
}
pub fn service_workers_mode(
mut self,
service_workers_mode: ServiceWorkersMode,
) -> RequestBuilder {
self.service_workers_mode = service_workers_mode;
self
}
pub fn cache_mode(mut self, cache_mode: CacheMode) -> RequestBuilder {
self.cache_mode = cache_mode;
self
}
pub fn build(self) -> Request {
let mut request = Request::new(
self.id,
self.url.clone(),
Some(self.origin),
self.referrer,
self.pipeline_id,
self.target_webview_id,
self.https_state,
);
request.preload_id = self.preload_id;
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.keep_alive = self.keep_alive;
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.cryptographic_nonce_metadata = self.cryptographic_nonce_metadata;
request.parser_metadata = self.parser_metadata;
request.response_tainting = self.response_tainting;
request.crash = self.crash;
request.client = self.client;
request.policy_container = self.policy_container;
request.insecure_requests_policy = self.insecure_requests_policy;
request.has_trustworthy_ancestor_origin = self.has_trustworthy_ancestor_origin;
request
}
pub fn keep_alive_body_length(&self) -> u64 {
assert!(self.keep_alive);
self.body.body_length() as u64
}
}
#[derive(Clone, MallocSizeOf)]
pub struct Request {
pub id: RequestId,
pub preload_id: Option<PreloadId>,
#[ignore_malloc_size_of = "Defined in hyper"]
pub method: Method,
pub local_urls_only: bool,
#[ignore_malloc_size_of = "Defined in hyper"]
pub headers: HeaderMap,
pub unsafe_request: bool,
pub body: Option<RequestBody>,
pub client: Option<RequestClient>,
pub traversable_for_user_prompts: TraversableForUserPrompts,
pub target_webview_id: Option<WebViewId>,
pub keep_alive: bool,
pub service_workers_mode: ServiceWorkersMode,
pub initiator: Initiator,
pub destination: Destination,
pub origin: Origin,
pub referrer: Referrer,
pub referrer_policy: ReferrerPolicy,
pub pipeline_id: Option<PipelineId>,
pub synchronous: bool,
pub mode: RequestMode,
pub use_cors_preflight: bool,
pub credentials_mode: CredentialsMode,
pub use_url_credentials: bool,
pub cache_mode: CacheMode,
pub redirect_mode: RedirectMode,
pub integrity_metadata: String,
pub cryptographic_nonce_metadata: String,
pub url_list: Vec<ServoUrl>,
pub redirect_count: u32,
pub response_tainting: ResponseTainting,
pub parser_metadata: ParserMetadata,
pub policy_container: RequestPolicyContainer,
pub insecure_requests_policy: InsecureRequestsPolicy,
pub has_trustworthy_ancestor_origin: bool,
pub https_state: HttpsState,
pub crash: Option<String>,
}
impl Request {
pub fn new(
id: RequestId,
url: ServoUrl,
origin: Option<Origin>,
referrer: Referrer,
pipeline_id: Option<PipelineId>,
webview_id: Option<WebViewId>,
https_state: HttpsState,
) -> Request {
Request {
id,
preload_id: None,
method: Method::GET,
local_urls_only: false,
headers: HeaderMap::new(),
unsafe_request: false,
body: None,
client: None,
traversable_for_user_prompts: TraversableForUserPrompts::Client,
keep_alive: false,
service_workers_mode: ServiceWorkersMode::All,
initiator: Initiator::None,
destination: Destination::None,
origin: origin.unwrap_or(Origin::Client),
referrer,
referrer_policy: ReferrerPolicy::EmptyString,
pipeline_id,
target_webview_id: webview_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(),
cryptographic_nonce_metadata: String::new(),
url_list: vec![url],
parser_metadata: ParserMetadata::Default,
redirect_count: 0,
response_tainting: ResponseTainting::Basic,
policy_container: RequestPolicyContainer::Client,
insecure_requests_policy: InsecureRequestsPolicy::DoNotUpgrade,
has_trustworthy_ancestor_origin: false,
https_state,
crash: None,
}
}
pub fn url(&self) -> ServoUrl {
self.url_list.first().unwrap().clone()
}
pub fn original_url(&self) -> ServoUrl {
match self.mode {
RequestMode::WebSocket {
protocols: _,
ref original_url,
} => original_url.clone(),
_ => self.url(),
}
}
pub fn current_url(&self) -> ServoUrl {
self.url_list.last().unwrap().clone()
}
pub fn current_url_mut(&mut self) -> &mut ServoUrl {
self.url_list.last_mut().unwrap()
}
pub fn is_navigation_request(&self) -> bool {
matches!(
self.destination,
Destination::Document |
Destination::Embed |
Destination::Frame |
Destination::IFrame |
Destination::Object
)
}
pub fn is_subresource_request(&self) -> bool {
matches!(
self.destination,
Destination::Audio |
Destination::Font |
Destination::Image |
Destination::Manifest |
Destination::Script |
Destination::Style |
Destination::Track |
Destination::Video |
Destination::Xslt |
Destination::None
)
}
pub fn timing_type(&self) -> ResourceTimingType {
if self.is_navigation_request() {
ResourceTimingType::Navigation
} else {
ResourceTimingType::Resource
}
}
pub fn populate_request_from_client(&mut self) {
if self.traversable_for_user_prompts == TraversableForUserPrompts::Client {
self.traversable_for_user_prompts = TraversableForUserPrompts::NoTraversable;
if self.client.is_some() {
self.traversable_for_user_prompts =
TraversableForUserPrompts::TraversableNavigable(Default::default());
}
}
if self.origin == Origin::Client {
let Some(client) = self.client.as_ref() else {
unreachable!();
};
self.origin = client.origin.clone();
}
if matches!(self.policy_container, RequestPolicyContainer::Client) {
if let Some(client) = self.client.as_ref() {
self.policy_container = client.policy_container.clone();
} else {
self.policy_container =
RequestPolicyContainer::PolicyContainer(PolicyContainer::default());
}
}
}
pub fn keep_alive_body_length(&self) -> u64 {
assert!(self.keep_alive);
self.body.body_length() as u64
}
pub fn total_request_length(&self) -> usize {
let mut total_request_length = self.url()[..Position::AfterQuery].len();
total_request_length += self
.referrer
.to_url()
.map(|url| url.as_str().len())
.unwrap_or_default();
total_request_length += self.headers.total_size();
total_request_length += self.body.body_length();
total_request_length
}
pub fn redirect_taint_for_request(&self) -> RedirectTaint {
let Origin::Origin(request_origin) = &self.origin else {
unreachable!("origin cannot be \"client\" at this point in time");
};
let mut last_url = None;
let mut taint = RedirectTaint::SameOrigin;
for url in &self.url_list {
let Some(last_url) = &mut last_url else {
last_url = Some(url);
continue;
};
if !is_same_site(&url.origin(), &last_url.origin()) &&
!is_same_site(request_origin, &last_url.origin())
{
return RedirectTaint::CrossSite;
}
if url.origin() != last_url.origin() && *request_origin != last_url.origin() {
taint = RedirectTaint::SameSite;
}
*last_url = url;
}
taint
}
}
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),
}
}
}
fn is_cors_unsafe_request_header_byte(value: &u8) -> bool {
matches!(value,
0x00..=0x08 |
0x10..=0x19 |
0x22 |
0x28 |
0x29 |
0x3A |
0x3C |
0x3E |
0x3F |
0x40 |
0x5B |
0x5C |
0x5D |
0x7B |
0x7D |
0x7F
)
}
fn is_cors_safelisted_request_accept(value: &[u8]) -> bool {
!(value.iter().any(is_cors_unsafe_request_header_byte))
}
fn is_cors_safelisted_language(value: &[u8]) -> bool {
value.iter().all(|&x| {
matches!(x,
0x30..=0x39 |
0x41..=0x5A |
0x61..=0x7A |
0x20 |
0x2A |
0x2C |
0x2D |
0x2E |
0x3B |
0x3D
)
})
}
pub fn is_cors_safelisted_request_content_type(value: &[u8]) -> bool {
if value.iter().any(is_cors_unsafe_request_header_byte) {
return false;
}
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, 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, },
}
}
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),
"range" => is_cors_safelisted_request_range(value),
_ => false,
}
}
pub fn is_cors_safelisted_request_range(value: &[u8]) -> bool {
if let Ok(value_str) = std::str::from_utf8(value) {
return validate_range_header(value_str);
}
false
}
fn validate_range_header(value: &str) -> bool {
let trimmed = value.trim();
if !trimmed.starts_with("bytes=") {
return false;
}
if let Some(range) = trimmed.strip_prefix("bytes=") {
let mut parts = range.split('-');
let start = parts.next();
let end = parts.next();
if let Some(start) = start {
if let Ok(start_num) = start.parse::<u64>() {
return match end {
Some(e) if !e.is_empty() => {
e.parse::<u64>().is_ok_and(|end_num| start_num <= end_num)
},
_ => true,
};
}
}
}
false
}
pub fn is_cors_safelisted_method(method: &Method) -> bool {
matches!(*method, Method::GET | Method::HEAD | Method::POST)
}
pub fn is_cors_non_wildcard_request_header_name(name: &HeaderName) -> bool {
name == AUTHORIZATION
}
pub fn get_cors_unsafe_header_names(headers: &HeaderMap) -> Vec<HeaderName> {
let mut unsafe_names: Vec<&HeaderName> = vec![];
let mut potentillay_unsafe_names: Vec<&HeaderName> = vec![];
let mut safelist_value_size = 0;
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();
}
}
if safelist_value_size > 1024 {
unsafe_names.extend_from_slice(&potentillay_unsafe_names);
}
convert_header_names_to_sorted_lowercase_set(unsafe_names)
}
pub fn convert_header_names_to_sorted_lowercase_set(
header_names: Vec<&HeaderName>,
) -> Vec<HeaderName> {
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();
ordered_set.into_iter().cloned().collect()
}
pub fn create_request_body_with_content(content: &str) -> RequestBody {
let content_bytes = GenericSharedMemory::from_bytes(content.as_bytes());
let content_len = content_bytes.len();
let (chunk_request_sender, chunk_request_receiver) = ipc::channel().unwrap();
ROUTER.add_typed_route(
chunk_request_receiver,
Box::new(move |message| {
let request = message.unwrap();
if let BodyChunkRequest::Connect(sender) = request {
let _ = sender.send(BodyChunkResponse::Chunk(content_bytes.clone()));
let _ = sender.send(BodyChunkResponse::Done);
}
}),
);
RequestBody::new(chunk_request_sender, BodySource::Object, Some(content_len))
}