#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![deny(clippy::all)]
use kvarn::{extensions::*, prelude::*};
#[cfg(feature = "reverse-proxy")]
#[path = "reverse-proxy.rs"]
pub mod reverse_proxy;
#[cfg(feature = "connection")]
pub use connection::Connection;
#[cfg(feature = "reverse-proxy")]
pub use reverse_proxy::{localhost, static_connection, Manager as ReverseProxy};
#[cfg(feature = "push")]
pub mod push;
#[cfg(feature = "push")]
pub use push::{mount as mount_push, SmartPush};
#[cfg(feature = "kvarn-fastcgi-client")]
pub mod fastcgi;
#[cfg(feature = "php")]
pub mod php;
#[cfg(feature = "php")]
pub use php::mount_php as php;
#[cfg(feature = "templates")]
pub mod templates;
#[cfg(feature = "templates")]
pub use templates::templates as templates_ext;
#[cfg(feature = "connection")]
pub mod connection;
#[cfg(feature = "certificate")]
pub mod certificate;
#[cfg(feature = "view-counter")]
#[path = "view-counter.rs"]
pub mod view_counter;
pub fn new() -> Extensions {
let mut e = Extensions::new();
mount_all(&mut e);
e
}
pub fn mount_all(extensions: &mut Extensions) {
#[cfg(feature = "templates")]
let template_cache = templates::Cache::new();
extensions.add_present_internal("download", Box::new(download));
extensions.add_present_internal("cache", Box::new(cache));
extensions.add_present_internal(
"hide",
hide(
#[cfg(feature = "templates")]
template_cache.clone(),
),
);
extensions.add_present_file(
"private",
hide(
#[cfg(feature = "templates")]
template_cache.clone(),
),
);
extensions.add_present_internal("allow-ips", Box::new(ip_allow));
#[cfg(feature = "templates")]
extensions.add_present_internal("tmpl", templates_ext(template_cache));
#[cfg(feature = "push")]
push::mount(extensions, SmartPush::default());
}
#[allow(dead_code)]
pub mod parse {
use super::*;
pub fn format_file_name<P: AsRef<Path>>(path: &P) -> Option<&str> {
path.as_ref().file_name().and_then(std::ffi::OsStr::to_str)
}
pub fn format_file_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf, io::Error> {
let mut file_path = std::env::current_dir()?;
file_path.push(path);
Ok(file_path)
}
}
pub fn download<'a>(data: &'a mut extensions::PresentData<'a>) -> RetFut<'a, ()> {
let headers = data.response.headers_mut();
headers.insert(
"content-type",
HeaderValue::from_static("application/octet-stream"),
);
ready(())
}
pub fn cache<'a>(data: &'a mut extensions::PresentData<'a>) -> RetFut<'a, ()> {
fn parse<'a, I: Iterator<Item = &'a str>>(
iter: I,
) -> (
Option<comprash::ClientCachePreference>,
Option<comprash::ServerCachePreference>,
) {
let mut c = None;
let mut s = None;
for arg in iter {
let mut parts = arg.split(':');
let domain = parts.next();
let cache = parts.next();
if let (Some(domain), Some(cache)) = (domain, cache) {
match domain {
"client" => {
if let Ok(preference) = cache.parse() {
c = Some(preference)
} else {
warn!("Parsing of client cache argument failed: {cache}");
}
}
"server" => {
if let Ok(preference) = cache.parse() {
s = Some(preference)
} else {
warn!("Parsing of client cache argument failed: {cache}");
}
}
_ => {
warn!(
"client extension used incorrectly, format: !> cache \
<client OR server>:<<integer>s OR ignore OR full \
OR changing OR none OR <empty>>"
);
}
}
}
}
(c, s)
}
let preference = parse(data.args.iter());
if let Some(c) = preference.0 {
*data.client_cache_preference = c;
}
if let Some(s) = preference.1 {
*data.server_cache_preference = s;
}
ready(())
}
pub fn hide(
#[cfg(feature = "templates")] template_cache: Arc<templates::Cache>,
) -> Box<dyn PresentCall> {
async fn inner(
data: &mut extensions::PresentData<'_>,
#[cfg(feature = "templates")] template_cache: &Arc<templates::Cache>,
) {
#[allow(unused_mut)] let mut error = default_error(StatusCode::NOT_FOUND, Some(data.host), None).await;
let arguments = utils::extensions::PresentExtensions::new(error.body().clone());
if let Some(arguments) = &arguments {
#[allow(unused_variables)] for argument in arguments.iter_clone() {
#[cfg(feature = "templates")]
if argument.name() == "tmpl" {
let mut error = error.map(|b| {
let mut c = utils::BytesCow::from(b);
c.replace(0..arguments.data_start(), b"");
c
});
templates::handle_template(
template_cache,
&argument,
error.body_mut(),
data.host,
)
.await;
*data.response = error;
return;
}
}
}
*data.response = error.map(Into::into);
}
#[cfg(not(feature = "templates"))]
{
present!(data, {
inner(data).await;
})
}
#[cfg(feature = "templates")]
{
present!(data, move |template_cache: Arc<templates::Cache>| {
inner(data, template_cache).await;
})
}
}
pub fn ip_allow<'a>(data: &'a mut extensions::PresentData<'a>) -> RetFut<'a, ()> {
box_fut!({
let mut matched = false;
for denied in data.args.iter() {
if let Ok(ip) = denied.parse::<IpAddr>() {
if data.address.ip() == ip {
matched = true;
break;
}
}
}
*data.server_cache_preference = comprash::ServerCachePreference::None;
*data.client_cache_preference = comprash::ClientCachePreference::Changing;
if !matched {
let error = default_error(StatusCode::NOT_FOUND, Some(data.host), None).await;
*data.response = error.map(Into::into);
}
})
}
pub type ForceCacheRules = Vec<(String, comprash::ClientCachePreference)>;
pub fn force_cache(extensions: &mut Extensions, rules: ForceCacheRules) {
let rules = Arc::new(rules);
let r1 = rules.clone();
fn resolve(
path: &str,
extension: Option<&str>,
rules: &ForceCacheRules,
) -> Option<comprash::ClientCachePreference> {
if let Some(extension) = extension {
for (rule, preference) in &**rules {
let replace = (rule.starts_with('/') && path.starts_with(rule))
|| rule.strip_prefix('.').map_or(false, |ext| ext == extension)
|| rule
.strip_prefix('*')
.and_then(|rule| rule.strip_suffix('*'))
.map_or(false, |rule| path.contains(rule));
if replace {
return Some(*preference);
}
}
}
None
}
extensions.add_present_fn(
Box::new(move |req, _host| {
let rules = &r1;
let extension = req.uri().path().split('.').last();
let path = req.uri().path();
resolve(path, extension, rules).is_some()
}),
present!(data, move |rules: Arc<ForceCacheRules>| {
let req = data.request;
let extension = req.uri().path().split('.').last();
let path = req.uri().path();
if let Some(preference) = resolve(path, extension, rules) {
*data.client_cache_preference = preference;
} else {
error!("(internal bug) force cache rules inconsistent")
}
}),
extensions::Id::new(16, "force_cache: Adding cache-control header").no_override(),
);
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn all() {
let extensions = new();
let _server = kvarn_testing::ServerBuilder::from(extensions).run().await;
}
}