#![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;
pub fn new() -> Extensions {
let mut e = Extensions::new();
mount_all(&mut e);
e
}
pub fn mount_all(extensions: &mut Extensions) {
extensions.add_present_internal("download", Box::new(download));
extensions.add_present_internal("cache", Box::new(cache));
extensions.add_present_internal("hide", Box::new(hide));
extensions.add_present_file("private", Box::new(hide));
extensions.add_present_internal("allow-ips", Box::new(ip_allow));
#[cfg(feature = "templates")]
extensions.add_present_internal("tmpl", Box::new(templates_ext));
#[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(data: &mut extensions::PresentData) -> RetFut<'_, ()> {
let headers = data.response_mut().headers_mut();
headers.insert(
"content-type",
HeaderValue::from_static("application/octet-stream"),
);
ready(())
}
pub fn cache(data: &mut extensions::PresentData) -> RetFut<'_, ()> {
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)
}
}
"server" => {
if let Ok(preference) = cache.parse() {
s = Some(preference)
}
}
_ => {}
}
}
}
(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(data: &mut extensions::PresentData) -> RetFut<'_, ()> {
box_fut!({
#[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() {
#[cfg(feature = "templates")]
if argument.name() == "tmpl" {
let body = templates::handle_template(
&argument,
&error.body()[arguments.data_start()..],
data.host(),
)
.await;
*error.body_mut() = body;
}
}
}
*data.response_mut() = error;
})
}
pub fn ip_allow(data: &mut extensions::PresentData) -> RetFut<'_, ()> {
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_mut() = error;
}
})
}
pub type ForceCacheRules = &'static [(&'static str, comprash::ClientCachePreference)];
pub fn force_cache(extensions: &mut Extensions, rules: ForceCacheRules) {
extensions.add_package(
package!(response, req, _, move |rules: ForceCacheRules| {
let extension = req.uri().path().split('.').last();
let path = req.uri().path();
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 {
if let Some(h) = preference.as_header() {
response.headers_mut().insert("cache-control", h);
}
}
}
}
}),
extensions::Id::new(16, "Adding cache-control header (force-cache)"),
);
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn all() {
let extensions = new();
let _server = kvarn_testing::ServerBuilder::from(extensions).run().await;
}
}