use crate::prelude::{internals::*, *};
pub const SERVER_NAME_VERSION: &str = "Kvarn/0.6.2";
#[cfg(feature = "uring")]
pub type RetFut<'a, T> = Pin<Box<(dyn Future<Output = T> + 'a)>>;
#[cfg(not(feature = "uring"))]
pub type RetFut<'a, T> = Pin<Box<(dyn Future<Output = T> + Send + 'a)>>;
#[cfg(feature = "uring")]
pub type RetSyncFut<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
#[cfg(not(feature = "uring"))]
pub type RetSyncFut<'a, T> = Pin<Box<dyn Future<Output = T> + Send + Sync + 'a>>;
#[cfg(feature = "uring")]
#[doc(hidden)]
pub trait KvarnSendSync {}
#[cfg(feature = "uring")]
impl<T> KvarnSendSync for T {}
#[cfg(not(feature = "uring"))]
#[doc(hidden)]
pub trait KvarnSendSync: Send + Sync {}
#[cfg(not(feature = "uring"))]
impl<T: Send + Sync> KvarnSendSync for T {}
pub type Prime = Box<dyn PrimeCall>;
pub trait PrimeCall: KvarnSendSync {
fn call<'a>(
&'a self,
request: &'a FatRequest,
host: &'a Host,
addr: SocketAddr,
) -> RetFut<'a, Option<Uri>>;
}
impl<
F: for<'a> Fn(&'a FatRequest, &'a Host, SocketAddr) -> RetFut<'a, Option<Uri>> + Send + Sync,
> PrimeCall for F
{
fn call<'a>(
&'a self,
request: &'a FatRequest,
host: &'a Host,
addr: SocketAddr,
) -> RetFut<'a, Option<Uri>> {
self(request, host, addr)
}
}
pub type Prepare = Box<dyn PrepareCall>;
pub trait PrepareCall: KvarnSendSync {
fn call<'a>(
&'a self,
request: &'a mut FatRequest,
host: &'a Host,
path: Option<&'a Path>,
addr: SocketAddr,
) -> RetFut<'a, FatResponse>;
}
impl<
F: for<'a> Fn(
&'a mut FatRequest,
&'a Host,
Option<&Path>,
SocketAddr,
) -> RetFut<'a, FatResponse>
+ KvarnSendSync,
> PrepareCall for F
{
fn call<'a>(
&'a self,
request: &'a mut FatRequest,
host: &'a Host,
path: Option<&Path>,
addr: SocketAddr,
) -> RetFut<'a, FatResponse> {
self(request, host, path, addr)
}
}
pub type Present = Box<dyn PresentCall>;
pub trait PresentCall: KvarnSendSync {
fn call<'a>(&'a self, present_data: &'a mut PresentData<'a>) -> RetFut<'a, ()>;
}
impl<F: for<'a> Fn(&'a mut PresentData<'a>) -> RetFut<'a, ()> + KvarnSendSync> PresentCall for F {
fn call<'a>(&'a self, present_data: &'a mut PresentData<'a>) -> RetFut<'a, ()> {
self(present_data)
}
}
pub type Package = Box<dyn PackageCall>;
pub trait PackageCall: KvarnSendSync {
fn call<'a>(
&'a self,
response: &'a mut Response<()>,
request: &'a FatRequest,
host: &'a Host,
addr: SocketAddr,
) -> RetFut<'a, ()>;
}
impl<
F: for<'a> Fn(&'a mut Response<()>, &'a FatRequest, &'a Host, SocketAddr) -> RetFut<'a, ()>
+ KvarnSendSync,
> PackageCall for F
{
fn call<'a>(
&'a self,
response: &'a mut Response<()>,
request: &'a FatRequest,
host: &'a Host,
addr: SocketAddr,
) -> RetFut<'a, ()> {
self(response, request, host, addr)
}
}
pub type Post = Box<dyn PostCall>;
pub trait PostCall: KvarnSendSync {
fn call<'a>(
&'a self,
request: &'a FatRequest,
host: &'a Host,
response_pipe: &'a mut ResponseBodyPipe,
identity_body: Bytes,
addr: SocketAddr,
) -> RetFut<'a, ()>;
}
impl<
F: for<'a> Fn(
&'a FatRequest,
&'a Host,
&'a mut ResponseBodyPipe,
Bytes,
SocketAddr,
) -> RetFut<'a, ()>
+ KvarnSendSync,
> PostCall for F
{
fn call<'a>(
&'a self,
request: &'a FatRequest,
host: &'a Host,
response_pipe: &'a mut ResponseBodyPipe,
identity_body: Bytes,
addr: SocketAddr,
) -> RetFut<'a, ()> {
self(request, host, response_pipe, identity_body, addr)
}
}
#[cfg(feature = "uring")]
pub type If = Box<(dyn Fn(&FatRequest, &Host) -> bool)>;
#[cfg(not(feature = "uring"))]
pub type If = Box<(dyn Fn(&FatRequest, &Host) -> bool + Sync + Send)>;
pub type ResponsePipeFuture = Box<dyn ResponsePipeFutureCall>;
pub trait ResponsePipeFutureCall: KvarnSendSync {
fn call<'a>(
&'a mut self,
response_body_pipe: &'a mut ResponseBodyPipe,
host: &'a Host,
) -> RetFut<'a, ()>;
}
#[derive(Debug, Clone, Copy)]
#[must_use]
pub struct Id {
priority: i32,
name: Option<&'static str>,
no_override: bool,
}
impl Id {
pub fn new(priority: i32, name: &'static str) -> Self {
Self {
priority,
name: Some(name),
no_override: false,
}
}
pub fn without_name(priority: i32) -> Self {
Self {
priority,
name: None,
no_override: false,
}
}
pub fn no_override(mut self) -> Self {
self.no_override = true;
self
}
#[must_use]
pub fn name(&self) -> &'static str {
self.name.unwrap_or("Unnamed")
}
#[must_use]
pub fn priority(&self) -> i32 {
self.priority
}
}
impl Display for Id {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "\"{}\" with priority {}", self.name(), self.priority())
}
}
impl PartialEq for Id {
fn eq(&self, other: &Self) -> bool {
self.priority().eq(&other.priority())
}
}
impl Eq for Id {}
impl Ord for Id {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.priority().cmp(&other.priority())
}
}
impl PartialOrd for Id {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
#[inline]
#[cfg(not(feature = "uring"))]
pub fn ready<'a, T: 'a + Send>(value: T) -> RetFut<'a, T> {
Box::pin(core::future::ready(value))
}
#[inline]
#[cfg(feature = "uring")]
pub fn ready<'a, T: 'a>(value: T) -> RetFut<'a, T> {
Box::pin(core::future::ready(value))
}
macro_rules! add_sorted_list {
($list: expr, $id: expr, $($other: expr, )+) => {
let mut id = $id;
loop {
match $list.binary_search_by(|probe| id.cmp(&probe.0)) {
Ok(_) if id.no_override => {
if let Some(priority) = id.priority.checked_sub(1) {
id.priority = priority;
} else {
panic!("reached minimum priority when trying to not override extension");
}
continue;
}
Ok(pos) => {
$list[pos] = (id, $($other, )*);
break;
}
Err(pos) => {
$list.insert(pos, (id, $($other, )*));
break;
}
};
}
};
}
macro_rules! remove_sorted_list {
($list: expr, $id: expr) => {
$list
.binary_search_by(|probe| probe.0.cmp(&$id))
.ok()
.map(|pos| $list.remove(pos))
};
}
#[must_use]
pub struct Extensions {
prime: Vec<(Id, Prime)>,
prepare_single: HashMap<CompactString, Prepare>,
prepare_fn: Vec<(Id, If, Prepare)>,
present_internal: HashMap<CompactString, Present>,
present_file: HashMap<CompactString, Present>,
present_fn: Vec<(Id, If, Present)>,
package: Vec<(Id, Package)>,
post: Vec<(Id, Post)>,
}
impl Extensions {
#[inline]
pub fn empty() -> Self {
Self {
prime: Vec::new(),
prepare_single: HashMap::new(),
prepare_fn: Vec::new(),
present_internal: HashMap::new(),
present_file: HashMap::new(),
present_fn: Vec::new(),
package: Vec::new(),
post: Vec::new(),
}
}
pub fn new() -> Self {
let mut new = Self::empty();
new.with_uri_redirect()
.with_no_referrer()
.with_disallow_cors()
.with_csp(Csp::default().arc())
.with_server_header(SERVER_NAME_VERSION, false, true);
#[cfg(feature = "nonce")]
{
new.with_nonce();
}
new
}
pub fn with_uri_redirect(&mut self) -> &mut Self {
self.add_prime(
prime!(request, host, _, {
enum Ending {
Dot,
Slash,
Other,
}
impl From<&Uri> for Ending {
fn from(uri: &Uri) -> Self {
if uri.path().ends_with('.') {
Self::Dot
} else if uri.path().ends_with('/') {
Self::Slash
} else {
Self::Other
}
}
}
let append = match Ending::from(request.uri()) {
Ending::Other => return None,
Ending::Dot => host.options.extension_default.as_deref().unwrap_or("html"),
Ending::Slash => host
.options
.folder_default
.as_deref()
.unwrap_or("index.html"),
};
let mut uri = request.uri().clone().into_parts();
let path = uri
.path_and_query
.as_ref()
.map_or("/", uri::PathAndQuery::path);
let query = uri
.path_and_query
.as_ref()
.and_then(uri::PathAndQuery::query);
let path_and_query = build_bytes!(
path.as_bytes(),
append.as_bytes(),
if query.is_none() { "" } else { "?" }.as_bytes(),
query.unwrap_or("").as_bytes()
);
uri.path_and_query =
Some(uri::PathAndQuery::from_maybe_shared(path_and_query).unwrap());
let uri = Uri::from_parts(uri).unwrap();
Some(uri)
}),
Id::new(-100, "Expand . and /"),
);
self
}
pub fn with_no_referrer(&mut self) -> &mut Self {
self.add_package(
package!(response, _, _, _, {
response
.headers_mut()
.entry("referrer-policy")
.or_insert(HeaderValue::from_static("no-referrer"));
}),
Id::new(10, "Set the referrer-policy header to no-referrer"),
);
self
}
#[cfg(feature = "https")]
pub fn with_http_to_https_redirect(&mut self) -> &mut Self {
self.add_prepare_fn(
Box::new(|request, host| {
request.uri().scheme_str() == Some("http") && request.uri().port().is_none() && {
host.certificate.read().unwrap().is_some()
}
}),
prepare!(request, _, _, _, {
let uri = request.uri();
let uri = {
let authority = uri.authority().map_or("", uri::Authority::as_str);
let bytes = build_bytes!(
b"https://",
authority.as_bytes(),
uri.path().as_bytes(),
uri.query().map_or(b"".as_ref(), |_| b"?".as_ref()),
uri.query().map_or(b"".as_ref(), str::as_bytes)
);
HeaderValue::from_maybe_shared(bytes).unwrap()
};
let response = Response::builder()
.status(StatusCode::TEMPORARY_REDIRECT)
.header("location", uri);
FatResponse::cache(response.body(Bytes::new()).unwrap())
.with_server_cache(comprash::ServerCachePreference::None)
.with_compress(comprash::CompressPreference::None)
}),
extensions::Id::new(86881, "Redirecting to HTTPS"),
);
self
}
#[cfg(feature = "nonce")]
pub fn with_nonce(&mut self) -> &mut Self {
use base64::Engine;
use rand::Rng;
const DEFAULT_ENGINE: base64::engine::GeneralPurpose = base64::engine::GeneralPurpose::new(
&base64::alphabet::STANDARD,
base64::engine::GeneralPurposeConfig::new().with_encode_padding(true),
);
self.add_present_internal(
"nonce",
present!(ext, {
let data: [u8; 16] = rand::thread_rng().gen();
let mut s = BytesMut::with_capacity(24);
unsafe { s.set_len(24) };
let wrote = DEFAULT_ENGINE
.encode_slice(data, &mut s)
.expect("base64 failed to encode");
assert_eq!(wrote, 24);
let body = ext.response.body_mut();
let mut replacement = Vec::with_capacity(28);
let mut last_start = 0;
while let Some(occurrence) =
memchr::memmem::find(&body[last_start + 1..], b"nonce=")
{
let occurrence = occurrence + last_start + 1;
let rest = &body[occurrence + 6..];
let first = rest.first();
let end = match first {
Some(b'"') => memchr::memchr(b'"', &rest[1..]),
Some(b'\'') => memchr::memchr(b'\'', &rest[1..]),
_ => None,
};
let end = end.map(|v| v + 1 + 6);
if let Some(end) = end {
let double = *first.unwrap() == b'"';
last_start = occurrence + end;
if double {
replacement.push(b'"');
} else {
replacement.push(b'\'');
}
replacement.extend_from_slice(&s);
if double {
replacement.push(b'"');
} else {
replacement.push(b'\'');
}
} else {
replacement.extend_from_slice(b"\"\"");
last_start = occurrence + 6 + 2;
}
body.replace(occurrence + 6..last_start, &replacement);
replacement.clear();
}
ext.response.headers_mut().insert(
"csp-nonce",
HeaderValue::from_maybe_shared(s.freeze())
.expect("base64 is valid for a header value"),
);
if *ext.server_cache_preference != comprash::ServerCachePreference::None {
error!(
"Enabled nonce on page with server caching enabled! \
This is critical for XSS resilience.\n\
nonces don't work with server caching."
);
*ext.server_cache_preference = comprash::ServerCachePreference::None;
}
}),
);
self
}
pub fn with_server_header(
&mut self,
server_name: impl AsRef<str>,
add_platform: bool,
override_server_header: bool,
) -> &mut Self {
#[cfg(target_os = "windows")]
const PLATFORM: &str = " (Windows)";
#[cfg(target_os = "macos")]
const PLATFORM: &str = " (macOS)";
#[cfg(target_os = "linux")]
const PLATFORM: &str = " (Linux)";
#[cfg(target_os = "freebsd")]
const PLATFORM: &str = " (FreeBSD)";
#[cfg(target_os = "netbsd")]
const PLATFORM: &str = " (NetBSD)";
#[cfg(target_os = "openbsd")]
const PLATFORM: &str = " (OpenBSD)";
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
)))]
const PLATFORM: &str = "";
let server_name = server_name.as_ref();
let bytes = build_bytes!(
server_name.as_bytes(),
if add_platform {
PLATFORM.as_bytes()
} else {
&[]
}
);
let header_value = HeaderValue::from_maybe_shared(bytes.freeze())
.expect("`server` header contains invalid bytes");
self.add_package(
package!(
resp,
_,
_,
_,
move |header_value: HeaderValue, override_server_header: bool| {
if *override_server_header {
resp.headers_mut().insert("server", header_value.clone());
} else {
resp.headers_mut().append("server", header_value.clone());
}
}
),
Id::new(-1327, "add `server` header"),
);
self
}
pub fn add_prime(&mut self, extension: Prime, id: Id) {
add_sorted_list!(self.prime, id, extension,);
}
pub fn remove_prime(&mut self, id: Id) {
remove_sorted_list!(self.prime, id);
}
pub fn get_prime(&self) -> &[(Id, Prime)] {
&self.prime
}
pub fn add_prepare_single(&mut self, path: impl AsRef<str>, extension: Prepare) {
self.prepare_single
.insert(path.as_ref().to_compact_string(), extension);
}
pub fn remove_prepare_single(&mut self, path: impl AsRef<str>) {
self.prepare_single.remove(path.as_ref());
}
#[must_use]
pub fn get_prepare_single(&self) -> &HashMap<CompactString, Prepare> {
&self.prepare_single
}
pub fn add_prepare_fn(&mut self, predicate: If, extension: Prepare, id: Id) {
add_sorted_list!(self.prepare_fn, id, predicate, extension,);
}
pub fn remove_prepare_fn(&mut self, id: Id) {
remove_sorted_list!(self.prepare_fn, id);
}
pub fn get_prepare_fn(&self) -> &[(Id, If, Prepare)] {
&self.prepare_fn
}
pub fn add_present_internal(&mut self, name: impl AsRef<str>, extension: Present) {
self.present_internal
.insert(name.as_ref().to_compact_string(), extension);
}
pub fn remove_present_internal(&mut self, path: impl AsRef<str>) {
self.present_internal.remove(path.as_ref());
}
#[must_use]
pub fn get_present_internal(&self) -> &HashMap<CompactString, Present> {
&self.present_internal
}
pub fn add_present_file(&mut self, name: impl AsRef<str>, extension: Present) {
self.present_file
.insert(name.as_ref().to_compact_string(), extension);
}
pub fn remove_present_file(&mut self, path: impl AsRef<str>) {
self.present_file.remove(path.as_ref());
}
#[must_use]
pub fn get_present_file(&self) -> &HashMap<CompactString, Present> {
&self.present_file
}
pub fn add_present_fn(&mut self, predicate: If, extension: Present, id: Id) {
add_sorted_list!(self.present_fn, id, predicate, extension,);
}
pub fn remove_present_fn(&mut self, id: Id) {
remove_sorted_list!(self.present_fn, id);
}
#[must_use]
pub fn get_present_fn(&self) -> &HashMap<CompactString, Present> {
&self.present_file
}
pub fn add_package(&mut self, extension: Package, id: Id) {
add_sorted_list!(self.package, id, extension,);
}
pub fn remove_package(&mut self, id: Id) {
remove_sorted_list!(self.package, id);
}
pub fn get_package(&self) -> &[(Id, Package)] {
&self.package
}
pub fn add_post(&mut self, extension: Post, id: Id) {
add_sorted_list!(self.post, id, extension,);
}
pub fn remove_post(&mut self, id: Id) {
remove_sorted_list!(self.post, id);
}
pub fn get_post(&self) -> &[(Id, Post)] {
&self.post
}
pub(crate) async fn resolve_prime(
&self,
request: &mut FatRequest,
host: &Host,
address: SocketAddr,
) -> Option<Uri> {
let mut uri = None;
for (_, prime) in &self.prime {
if let Some(prime) = prime.call(request, host, address).await {
if prime.path().starts_with("/./") {
uri = Some(prime);
} else {
*request.uri_mut() = prime;
}
}
}
uri
}
pub(crate) async fn resolve_prepare(
&self,
request: &mut FatRequest,
overide_uri: Option<&Uri>,
host: &Host,
path: &Option<CompactString>,
address: SocketAddr,
) -> Option<FatResponse> {
if let Some(extension) = self
.prepare_single
.get(overide_uri.unwrap_or_else(|| request.uri()).path())
{
Some(
extension
.call(request, host, path.as_deref().map(Path::new), address)
.await,
)
} else {
for (_, function, extension) in &self.prepare_fn {
if function(request, host) {
return Some(
extension
.call(request, host, path.as_deref().map(Path::new), address)
.await,
);
}
}
None
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn resolve_present(
&self,
request: &mut Request<Body>,
response: &mut Response<Bytes>,
client_cache_preference: &mut comprash::ClientCachePreference,
server_cache_preference: &mut comprash::ServerCachePreference,
host: &Host,
address: SocketAddr,
) {
let mut body = LazyRequestBody::new(request.body_mut());
let body = &mut body;
let path = utils::parse::uri(request.uri().path());
let extensions = PresentExtensions::new(Bytes::clone(response.body()));
if let Some(extensions) = &extensions {
*response.body_mut() = response.body_mut().split_off(extensions.data_start());
}
let (response_head, response_body) = utils::split_response(core::mem::take(response));
let response_body = utils::BytesCow::Ref(response_body);
let mut cow_response = response_head.map(|()| response_body);
for (_, predicate, ext) in &self.present_fn {
if (predicate)(request, host) {
let mut data = PresentData {
address,
request,
body,
host,
path: path.map(Path::new),
server_cache_preference,
client_cache_preference,
response: &mut cow_response,
args: PresentArguments::empty(),
};
ext.call(&mut data).await;
}
}
if let Some(extension) = path
.map(Path::new)
.and_then(Path::extension)
.and_then(std::ffi::OsStr::to_str)
.and_then(|s| self.present_file.get(s))
{
let mut data = PresentData {
address,
request,
body,
host,
path: path.map(Path::new),
server_cache_preference,
client_cache_preference,
response: &mut cow_response,
args: PresentArguments::empty(),
};
extension.call(&mut data).await;
}
if let Some(extensions) = extensions {
for extension_name_args in extensions {
if let Some(extension) = self.present_internal.get(extension_name_args.name()) {
let mut data = PresentData {
address,
request,
body,
host,
path: path.map(Path::new),
server_cache_preference,
client_cache_preference,
response: &mut cow_response,
args: extension_name_args,
};
extension.call(&mut data).await;
}
}
}
*response = cow_response.map(utils::BytesCow::freeze);
}
pub(crate) async fn resolve_package(
&self,
response: &mut Response<()>,
request: &FatRequest,
host: &Host,
addr: SocketAddr,
) {
for (_, extension) in &self.package {
extension.call(response, request, host, addr).await;
}
}
pub(crate) async fn resolve_post(
&self,
request: &FatRequest,
bytes: Bytes,
response_pipe: &mut ResponseBodyPipe,
addr: SocketAddr,
host: &Host,
) {
for (_, extension) in self.post.iter().take(self.post.len().saturating_sub(1)) {
extension
.call(request, host, response_pipe, Bytes::clone(&bytes), addr)
.await;
}
if let Some((_, extension)) = self.post.last() {
extension
.call(request, host, response_pipe, bytes, addr)
.await;
}
}
}
impl Default for Extensions {
fn default() -> Self {
Self::new()
}
}
impl Debug for Extensions {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
macro_rules! map {
($slice: expr) => {
&$slice
.iter()
.map(|ext| (ext.0.as_clean(), "internal extension".as_clean()))
.collect::<Vec<_>>()
};
}
let mut s = f.debug_struct(utils::ident_str!(Extensions));
utils::fmt_fields!(
s,
(self.prime, map!(self.prime)),
(self.prepare_single, map!(self.prepare_single)),
(self.prepare_fn, map!(self.prepare_fn)),
(self.present_internal, map!(self.present_internal)),
(self.present_file, map!(self.present_file)),
(self.present_fn, map!(self.present_fn)),
(self.package, map!(self.package)),
(self.post, map!(self.post)),
);
s.finish()
}
}
#[allow(missing_docs)]
#[derive(Debug)]
pub struct PresentData<'a> {
pub address: SocketAddr,
pub request: &'a FatRequest,
pub body: &'a mut LazyRequestBody,
pub host: &'a Host,
pub path: Option<&'a Path>,
pub server_cache_preference: &'a mut comprash::ServerCachePreference,
pub client_cache_preference: &'a mut comprash::ClientCachePreference,
pub response: &'a mut Response<utils::BytesCow>,
pub args: PresentArguments,
}
#[derive(Debug)]
#[must_use]
pub struct LazyRequestBody {
body: *mut Body,
result: Option<Bytes>,
}
impl LazyRequestBody {
#[inline]
pub(crate) fn new(body: &mut Body) -> Self {
Self { body, result: None }
}
#[inline]
pub async fn get(&mut self, max_len: usize) -> io::Result<&Bytes> {
if let Some(ref result) = self.result {
Ok(result)
} else {
let buffer = unsafe { &mut *self.body }.read_to_bytes(max_len).await?;
self.result.replace(buffer);
Ok(self.result.as_ref().unwrap())
}
}
}
unsafe impl Send for LazyRequestBody {}
unsafe impl Sync for LazyRequestBody {}
#[must_use]
#[derive(Debug, Clone)]
pub struct RuleSet<R> {
rules: Vec<(String, R)>,
}
impl<R> RuleSet<R> {
pub fn empty() -> Self {
Self { rules: Vec::new() }
}
pub fn add(mut self, path: impl AsRef<str>, rule: impl Into<R>) -> Self {
self.add_mut(path, rule);
self
}
pub fn add_mut(&mut self, path: impl AsRef<str>, rule: impl Into<R>) -> &mut Self {
let path = path.as_ref().to_owned();
if let Ok(idx) = self.rules.binary_search_by(|probe| probe.0.cmp(&path)) {
self.rules.remove(idx);
}
self.rules.push((path, rule.into()));
self.rules.sort_unstable_by(|a, b| {
use std::cmp::Ordering;
if a.0.ends_with('*') == b.0.ends_with('*') {
b.0.len().cmp(&a.0.len())
} else if a.0.ends_with('*') {
Ordering::Greater
} else {
Ordering::Less
}
});
self
}
#[must_use]
pub fn arc(self) -> Arc<Self> {
Arc::new(self)
}
#[must_use]
pub fn get(&self, uri_path: &str) -> Option<&R> {
for (path, allow) in &self.rules {
if path == uri_path
|| (path
.strip_suffix('*')
.map_or(false, |path| uri_path.starts_with(path)))
{
return Some(allow);
}
}
None
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation, unused_mut)]
pub fn stream_body() -> Box<dyn PrepareCall> {
prepare!(req, host, path, _addr, {
debug!("Streaming body for {:?}", req.uri().path());
if let Some(path) = path {
let file = fs::File::open(path).await;
let meta = if let Ok(_file) = &file {
#[cfg(feature = "uring")]
{
tokio_uring::fs::statx(path).await.ok()
}
#[cfg(not(feature = "uring"))]
{
_file.metadata().await.ok()
}
} else {
None
};
if let (Ok(mut file), Some(meta)) = (file, meta) {
let mut response = Response::new(Bytes::new());
response
.headers_mut()
.insert("vary", HeaderValue::from_static("range"));
let first_bytes = {
let mut v = vec![0; 16];
#[cfg(feature = "uring")]
let (Ok(read), mut v) = file.read_at(v, 0).await
else {
return default_error_response(StatusCode::NOT_FOUND, host, None).await;
};
#[cfg(not(feature = "uring"))]
let Ok(read) = file.read(&mut v).await
else {
return default_error_response(StatusCode::NOT_FOUND, host, None).await;
};
v.truncate(read);
v
};
if !response.headers().contains_key("content-type") {
let mime = comprash::get_mime(
path.extension()
.and_then(std::ffi::OsStr::to_str)
.unwrap_or(""),
&first_bytes,
);
let mime = if comprash::is_text(&mime) {
let b = mime.to_string().into_bytes();
build_bytes!(&b, b"; charset=utf-8").freeze()
} else {
mime.to_string().into_bytes().into()
};
let content_type = HeaderValue::from_maybe_shared::<Bytes>(mime).unwrap();
response.headers_mut().insert("content-type", content_type);
}
#[cfg(feature = "uring")]
let len = meta.stx_size as usize;
#[cfg(not(feature = "uring"))]
let len = meta.len() as usize;
#[allow(clippy::uninit_vec)]
let fut = response_pipe_fut!(response, _host, move |file: fs::File| {
let mut buf = Vec::with_capacity(1024 * 64);
#[cfg(feature = "uring")]
let mut pos = 0;
unsafe { buf.set_len(buf.capacity()) };
let mut i = 0u32;
loop {
i = i.wrapping_add(1);
#[cfg(feature = "uring")]
let r = {
let (r, b) = file.read_at(buf, pos).await;
buf = b;
r
};
#[cfg(not(feature = "uring"))]
let r = file.read(&mut buf).await;
match r {
Ok(read) => {
if read == 0 {
break;
}
#[cfg(feature = "uring")]
{
pos += read as u64;
}
let data = Bytes::copy_from_slice(&buf[..read]);
let r = if i % 160 == 0 {
response.send_with_wait(data, 10 * 1024 * 1024).await
} else {
response.send(data).await
};
match r {
Ok(()) => {}
Err(_) => {
break;
}
}
}
Err(err) => {
warn!("Failed to stream body from file: {err}");
break;
}
}
}
});
FatResponse::new(response, comprash::ServerCachePreference::None)
.with_future_and_len(fut, len)
} else {
default_error_response(StatusCode::NOT_FOUND, host, None).await
}
} else {
default_error_response(StatusCode::NOT_FOUND, host, None).await
}
})
}
mod macros {
#[macro_export]
macro_rules! box_fut {
($code:block) => {
Box::pin(async move { $code }) as $crate::extensions::RetFut<_>
};
}
#[macro_export]
macro_rules! extension {
($trait: ty, $ret: ty, $(($meta:tt) ,)? | $($param:tt: $param_type:ty: $param_type_no_lifetimes:ty :$name:ident ),* |, $(($($(($mut:tt))? $move:ident:$ty:ty),+))?, $code:block) => {{
#[cfg(feature = "uring")]
struct Ext<F: for<'a> Fn($($param_type,)* $($(&'a $($mut)? $ty,)+)?) -> $crate::extensions::RetFut<'a, $ret>> {
function_private: F,
$($($move:$ty,)+)?
}
#[cfg(feature = "uring")]
impl<F: for<'a> Fn($($param_type,)* $($(&'a $($mut)? $ty,)+)?) -> $crate::extensions::RetFut<'a, $ret>> $trait for Ext<F> {
fn call<'a>(
&'a $($meta)? self,
$($name: $param_type,)*
) -> $crate::extensions::RetFut<'a, $ret> {
let Self {
function_private,
$($($move,)+)?
} = self;
(function_private)($($name,)* $($($move,)+)?)
}
}
#[cfg(not(feature = "uring"))]
struct Ext<F: for<'a> Fn($($param_type,)* $($(&'a $($mut)? $ty,)+)?) -> $crate::extensions::RetFut<'a, $ret> + Send + Sync> {
function_private: F,
$($($move:$ty,)+)?
}
#[cfg(not(feature = "uring"))]
impl<F: for<'a> Fn($($param_type,)* $($(&'a $($mut)? $ty,)+)?) -> $crate::extensions::RetFut<'a, $ret> + Send + Sync> $trait for Ext<F> {
fn call<'a>(
&'a $($meta)? self,
$($name: $param_type,)*
) -> $crate::extensions::RetFut<'a, $ret> {
let Self {
function_private,
$($($move,)+)?
} = self;
(function_private)($($name,)* $($($move,)+)?)
}
}
Box::new(Ext {
function_private: move |$($param: $param_type_no_lifetimes,)* $($($move: & $($mut)? $ty,)+)?| {
Box::pin(async move {
$code
})
},
$($($move,)+)?
})
}};
}
#[macro_export]
macro_rules! prime {
($request:pat, $host:pat, $addr:pat, $(move |$($move:ident:$ty:ty ),+|)? $code:block) => {
$crate::extension!(
$crate::extensions::PrimeCall,
Option<$crate::prelude::Uri>,
|$request: &'a $crate::FatRequest: &$crate::FatRequest: a1,
$host: &'a $crate::prelude::Host: &$crate::prelude::Host: a2,
$addr: $crate::prelude::SocketAddr: $crate::prelude::SocketAddr: a3|,
$(($($move:$ty),+))?,
$code
) as $crate::extensions::Prime
}
}
#[macro_export]
macro_rules! prepare {
($request:pat, $host:pat, $path:pat, $addr:pat, $(move |$($move:ident:$ty:ty),+|)? $code:block) => {
$crate::extension!($crate::extensions::PrepareCall, $crate::FatResponse, |
$request: &'a mut $crate::FatRequest: &mut $crate::FatRequest: a1,
$host: &'a $crate::prelude::Host: &$crate::prelude::Host: a2,
$path: Option<&'a $crate::prelude::Path>: Option<&$crate::prelude::Path>: a3,
$addr: $crate::prelude::SocketAddr: $crate::prelude::SocketAddr: a4 |,
$(($($move:$ty),+))?,
$code
) as $crate::extensions::Prepare
}
}
#[macro_export]
macro_rules! present {
($data:pat, $(move |$($move:ident:$ty:ty ),+|)? $code:block) => {
$crate::extension!(
$crate::extensions::PresentCall,
(),
|$data: &'a mut $crate::extensions::PresentData<'a>: &mut $crate::extensions::PresentData: a1|,
$(($($move:$ty),+))?,
$code
) as $crate::extensions::Present
}
}
#[macro_export]
macro_rules! package {
($response:pat, $request:pat, $host:pat, $addr:pat, $(move |$($move:ident:$ty:ty ),+|)? $code:block) => {
$crate::extension!(
$crate::extensions::PackageCall,
(),
|$response: &'a mut $crate::prelude::Response<()>: &mut $crate::prelude::Response<()>: a1,
$request: &'a $crate::FatRequest: &$crate::FatRequest: a2,
$host: &'a $crate::prelude::Host: &$crate::prelude::Host: a3,
$addr: $crate::prelude::SocketAddr: $crate::prelude::SocketAddr: a4 |,
$(($($move:$ty),+))?,
$code
) as $crate::extensions::Package
}
}
#[macro_export]
macro_rules! post {
($request:pat, $host:pat, $response_pipe:pat, $bytes:pat, $addr:pat, $(move |$($move:ident:$ty:ty ),+|)? $code:block) => {
$crate::extension!(
$crate::extensions::PostCall,
(),
|$request: &'a $crate::FatRequest: &$crate::FatRequest: a1,
$host: &'a $crate::prelude::Host: &$crate::prelude::Host: a2,
$response_pipe: &'a mut $crate::application::ResponseBodyPipe: &mut $crate::application::ResponseBodyPipe: a3,
$bytes: $crate::prelude::Bytes: $crate::prelude::Bytes: a4,
$addr: $crate::prelude::SocketAddr: $crate::prelude::SocketAddr: a5|,
$(($($move:$ty),+))?,
$code
) as $crate::extensions::Post
}
}
#[macro_export]
macro_rules! response_pipe_fut {
($response:pat, $host:pat, $(move |$($move:ident:$ty:ty),+|)? $code:block) => {
$crate::extension!(
$crate::extensions::ResponsePipeFutureCall,
(),
(mut),
|$response: &'a mut $crate::application::ResponseBodyPipe: &mut $crate::application::ResponseBodyPipe: a1,
$host: &'a $crate::prelude::Host: &$crate::prelude::Host: a2|,
$(($((mut) $move:$ty),+))?, $code)
};
}
}