use std::collections::hash_map::Entry;
use std::future;
use std::future::Future;
use std::ops::Bound;
use std::pin::Pin;
use headers::Range;
use http::StatusCode;
use log::error;
use net_traits::filemanager_thread::RelativePos;
use net_traits::request::Request;
use net_traits::response::Response;
use net_traits::{DiscardFetch, NetworkError};
use rustc_hash::FxHashMap;
use servo_url::ServoUrl;
use url::Position;
use crate::fetch::methods::{DoneChannel, FetchContext, RangeRequestBounds, fetch};
mod blob;
mod data;
mod file;
use blob::BlobProtocolHander;
use data::DataProtocolHander;
use file::FileProtocolHander;
type FutureResponse<'a> = Pin<Box<dyn Future<Output = Response> + Send + 'a>>;
static FORBIDDEN_SCHEMES: [&str; 4] = ["http", "https", "chrome", "about"];
pub trait ProtocolHandler: Send + Sync {
fn privileged_paths(&self) -> &'static [&'static str] {
&[]
}
fn load<'a>(
&'a self,
request: &'a mut Request,
done_chan: &mut DoneChannel,
context: &FetchContext,
) -> FutureResponse<'a>;
fn is_fetchable(&self) -> bool {
false
}
fn is_secure(&self) -> bool {
false
}
}
#[derive(Default)]
pub struct ProtocolRegistry {
pub(crate) handlers: FxHashMap<String, Box<dyn ProtocolHandler>>, }
#[derive(Clone, Copy, Debug)]
pub enum ProtocolRegisterError {
ForbiddenScheme,
SchemeAlreadyRegistered,
}
impl ProtocolRegistry {
pub fn with_internal_protocols() -> Self {
let mut registry = Self::default();
registry
.register("data", DataProtocolHander::default())
.expect("Infallible");
registry
.register("blob", BlobProtocolHander::default())
.expect("Infallible");
registry
.register("file", FileProtocolHander::default())
.expect("Infallible");
registry
}
pub fn register(
&mut self,
scheme: &str,
handler: impl ProtocolHandler + 'static,
) -> Result<(), ProtocolRegisterError> {
if FORBIDDEN_SCHEMES.contains(&scheme) {
error!("Protocol handler for '{scheme}' is not allowed to be registered.");
return Err(ProtocolRegisterError::ForbiddenScheme);
}
if let Entry::Vacant(entry) = self.handlers.entry(scheme.into()) {
entry.insert(Box::new(handler));
Ok(())
} else {
error!("Protocol handler for '{scheme}' is already registered.");
Err(ProtocolRegisterError::SchemeAlreadyRegistered)
}
}
pub fn register_page_content_handler(
&mut self,
scheme: String,
url: String,
) -> Result<(), ProtocolRegisterError> {
self.register(
&scheme.clone(),
WebPageContentProtocolHandler { url, scheme },
)
}
pub fn get(&self, scheme: &str) -> Option<&dyn ProtocolHandler> {
self.handlers.get(scheme).map(|e| e.as_ref())
}
pub fn merge(&mut self, mut other: ProtocolRegistry) {
for (scheme, handler) in other.handlers.drain() {
if FORBIDDEN_SCHEMES.contains(&scheme.as_str()) {
error!("Protocol handler for '{scheme}' is not allowed to be registered.");
continue;
}
self.handlers.entry(scheme).or_insert(handler);
}
}
pub fn is_fetchable(&self, scheme: &str) -> bool {
self.handlers
.get(scheme)
.is_some_and(|handler| handler.is_fetchable())
}
pub fn is_secure(&self, scheme: &str) -> bool {
self.handlers
.get(scheme)
.is_some_and(|handler| handler.is_secure())
}
pub fn privileged_urls(&self) -> Vec<ServoUrl> {
self.handlers
.iter()
.flat_map(|(scheme, handler)| {
let paths = handler.privileged_paths();
paths
.iter()
.filter_map(move |path| ServoUrl::parse(&format!("{scheme}:{path}")).ok())
})
.collect()
}
}
struct WebPageContentProtocolHandler {
url: String,
scheme: String,
}
impl ProtocolHandler for WebPageContentProtocolHandler {
fn load<'a>(
&'a self,
request: &'a mut Request,
_done_chan: &mut DoneChannel,
context: &FetchContext,
) -> FutureResponse<'a> {
let mut url = request.current_url();
assert!(url.scheme() == self.scheme);
let _ = url.set_username("");
let _ = url.set_password(None);
let encoded_url = &url[Position::AfterScheme..][1..];
let handler_url_string = self.url.replacen("%s", encoded_url, 1);
let Ok(result_url) = ServoUrl::parse(&handler_url_string) else {
return Box::pin(future::ready(Response::network_error(
NetworkError::ProtocolHandlerSubstitutionError,
)));
};
assert!(matches!(result_url.scheme(), "http" | "https"));
request.url_list.push(result_url);
let request2 = request.clone();
let context2 = context.clone();
Box::pin(async move { fetch(request2, &mut DiscardFetch, &context2).await })
}
}
pub fn is_url_potentially_trustworthy(
protocol_registry: &ProtocolRegistry,
url: &ServoUrl,
) -> bool {
url.is_potentially_trustworthy() || protocol_registry.is_secure(url.scheme())
}
pub fn range_not_satisfiable_error(response: &mut Response) {
response.status = StatusCode::RANGE_NOT_SATISFIABLE.into();
}
pub fn get_range_request_bounds(range: Option<Range>, len: u64) -> RangeRequestBounds {
if let Some(ref range) = range {
let (start, end) = match range.satisfiable_ranges(len).next() {
Some((Bound::Included(start), Bound::Unbounded)) => (start, None),
Some((Bound::Included(start), Bound::Included(end))) => {
(start, Some(i64::max(start as i64, end as i64)))
},
Some((Bound::Unbounded, Bound::Included(offset))) => {
return RangeRequestBounds::Pending(offset);
},
_ => (0, None),
};
RangeRequestBounds::Final(RelativePos::from_opts(Some(start as i64), end))
} else {
RangeRequestBounds::Final(RelativePos::from_opts(Some(0), None))
}
}
pub fn partial_content(response: &mut Response) {
response.status = StatusCode::PARTIAL_CONTENT.into();
}