1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
12#![deny(clippy::all)]
13
14use kvarn::{extensions::*, prelude::*};
15
16#[cfg(feature = "reverse-proxy")]
17#[path = "reverse-proxy.rs"]
18pub mod reverse_proxy;
19#[cfg(feature = "connection")]
20pub use connection::Connection;
21#[cfg(feature = "reverse-proxy")]
22pub use reverse_proxy::{localhost, static_connection, Manager as ReverseProxy};
23
24#[cfg(feature = "push")]
25pub mod push;
26#[cfg(feature = "push")]
27pub use push::{mount as mount_push, SmartPush};
28
29#[cfg(feature = "kvarn-fastcgi-client")]
30pub mod fastcgi;
31
32#[cfg(feature = "php")]
33pub mod php;
34#[cfg(feature = "php")]
35pub use php::mount_php as php;
36
37#[cfg(feature = "templates")]
38pub mod templates;
39#[cfg(feature = "templates")]
40pub use templates::templates as templates_ext;
41
42#[cfg(feature = "connection")]
43pub mod connection;
44
45#[cfg(feature = "certificate")]
46pub mod certificate;
47
48#[cfg(feature = "view-counter")]
49#[path = "view-counter.rs"]
50pub mod view_counter;
51
52pub fn new() -> Extensions {
56 let mut e = Extensions::new();
57 mount_all(&mut e);
58 e
59}
60
61pub fn mount_all(extensions: &mut Extensions) {
93 #[cfg(feature = "templates")]
94 let template_cache = templates::Cache::new();
95 extensions.add_present_internal("download", Box::new(download));
96 extensions.add_present_internal("cache", Box::new(cache));
97 extensions.add_present_internal(
98 "hide",
99 hide(
100 #[cfg(feature = "templates")]
101 template_cache.clone(),
102 ),
103 );
104 extensions.add_present_file(
105 "private",
106 hide(
107 #[cfg(feature = "templates")]
108 template_cache.clone(),
109 ),
110 );
111 extensions.add_present_internal("allow-ips", Box::new(ip_allow));
112 #[cfg(feature = "templates")]
113 extensions.add_present_internal("tmpl", templates_ext(template_cache));
114 #[cfg(feature = "push")]
115 push::mount(extensions, SmartPush::default());
116}
117
118#[allow(dead_code)]
120pub mod parse {
121 use super::*;
122
123 pub fn format_file_name<P: AsRef<Path>>(path: &P) -> Option<&str> {
124 path.as_ref().file_name().and_then(std::ffi::OsStr::to_str)
125 }
126 pub fn format_file_path<P: AsRef<Path>>(path: &P) -> Result<PathBuf, io::Error> {
127 let mut file_path = std::env::current_dir()?;
128 file_path.push(path);
129 Ok(file_path)
130 }
131}
132
133pub fn download<'a>(data: &'a mut extensions::PresentData<'a>) -> RetFut<'a, ()> {
135 let headers = data.response.headers_mut();
136 headers.insert(
137 "content-type",
138 HeaderValue::from_static("application/octet-stream"),
139 );
140 ready(())
141}
142
143pub fn cache<'a>(data: &'a mut extensions::PresentData<'a>) -> RetFut<'a, ()> {
144 fn parse<'a, I: Iterator<Item = &'a str>>(
145 iter: I,
146 ) -> (
147 Option<comprash::ClientCachePreference>,
148 Option<comprash::ServerCachePreference>,
149 ) {
150 let mut c = None;
151 let mut s = None;
152 for arg in iter {
153 let mut parts = arg.split(':');
154 let domain = parts.next();
155 let cache = parts.next();
156 if let (Some(domain), Some(cache)) = (domain, cache) {
157 match domain {
158 "client" => {
159 if let Ok(preference) = cache.parse() {
160 c = Some(preference)
161 } else {
162 warn!("Parsing of client cache argument failed: {cache}");
163 }
164 }
165 "server" => {
166 if let Ok(preference) = cache.parse() {
167 s = Some(preference)
168 } else {
169 warn!("Parsing of client cache argument failed: {cache}");
170 }
171 }
172 _ => {
173 warn!(
174 "client extension used incorrectly, format: !> cache \
175 <client OR server>:<<integer>s OR ignore OR full \
176 OR changing OR none OR <empty>>"
177 );
178 }
179 }
180 }
181 }
182 (c, s)
183 }
184 let preference = parse(data.args.iter());
185 if let Some(c) = preference.0 {
186 *data.client_cache_preference = c;
187 }
188 if let Some(s) = preference.1 {
189 *data.server_cache_preference = s;
190 }
191 ready(())
192}
193
194pub fn hide(
195 #[cfg(feature = "templates")] template_cache: Arc<templates::Cache>,
196) -> Box<dyn PresentCall> {
197 async fn inner(
198 data: &mut extensions::PresentData<'_>,
199 #[cfg(feature = "templates")] template_cache: &Arc<templates::Cache>,
200 ) {
201 #[allow(unused_mut)] let mut error = default_error(StatusCode::NOT_FOUND, Some(data.host), None).await;
203 let arguments = utils::extensions::PresentExtensions::new(error.body().clone());
204 if let Some(arguments) = &arguments {
205 #[allow(unused_variables)] for argument in arguments.iter_clone() {
207 #[cfg(feature = "templates")]
208 if argument.name() == "tmpl" {
209 let mut error = error.map(|b| {
210 let mut c = utils::BytesCow::from(b);
211 c.replace(0..arguments.data_start(), b"");
212 c
213 });
214 templates::handle_template(
215 template_cache,
216 &argument,
217 error.body_mut(),
218 data.host,
219 )
220 .await;
221 *data.response = error;
222 return;
223 }
224 }
225 }
226
227 *data.response = error.map(Into::into);
228 }
229 #[cfg(not(feature = "templates"))]
230 {
231 present!(data, {
232 inner(data).await;
233 })
234 }
235 #[cfg(feature = "templates")]
236 {
237 present!(data, move |template_cache: Arc<templates::Cache>| {
238 inner(data, template_cache).await;
239 })
240 }
241}
242
243pub fn ip_allow<'a>(data: &'a mut extensions::PresentData<'a>) -> RetFut<'a, ()> {
244 box_fut!({
245 let mut matched = false;
246 for denied in data.args.iter() {
248 if let Ok(ip) = denied.parse::<IpAddr>() {
250 if data.address.ip() == ip {
252 matched = true;
253 break;
255 }
256 }
257 }
258 *data.server_cache_preference = comprash::ServerCachePreference::None;
259 *data.client_cache_preference = comprash::ClientCachePreference::Changing;
260
261 if !matched {
262 let error = default_error(StatusCode::NOT_FOUND, Some(data.host), None).await;
264 *data.response = error.map(Into::into);
265 }
266 })
267}
268
269pub type ForceCacheRules = Vec<(String, comprash::ClientCachePreference)>;
281pub fn force_cache(extensions: &mut Extensions, rules: ForceCacheRules) {
282 let rules = Arc::new(rules);
283 let r1 = rules.clone();
284
285 fn resolve(
286 path: &str,
287 extension: Option<&str>,
288 rules: &ForceCacheRules,
289 ) -> Option<comprash::ClientCachePreference> {
290 if let Some(extension) = extension {
291 for (rule, preference) in &**rules {
292 let replace = (rule.starts_with('/') && path.starts_with(rule))
293 || rule.strip_prefix('.').map_or(false, |ext| ext == extension)
294 || rule
295 .strip_prefix('*')
296 .and_then(|rule| rule.strip_suffix('*'))
297 .map_or(false, |rule| path.contains(rule));
298 if replace {
299 return Some(*preference);
300 }
301 }
302 }
303 None
304 }
305
306 extensions.add_present_fn(
307 Box::new(move |req, _host| {
308 let rules = &r1;
309 let extension = req.uri().path().split('.').last();
310 let path = req.uri().path();
311 resolve(path, extension, rules).is_some()
312 }),
313 present!(data, move |rules: Arc<ForceCacheRules>| {
314 let req = data.request;
315 let extension = req.uri().path().split('.').last();
316 let path = req.uri().path();
317 if let Some(preference) = resolve(path, extension, rules) {
318 *data.client_cache_preference = preference;
319 } else {
320 error!("(internal bug) force cache rules inconsistent")
321 }
322 }),
323 extensions::Id::new(16, "force_cache: Adding cache-control header").no_override(),
324 );
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330 #[tokio::test]
331 async fn all() {
332 let extensions = new();
333 let _server = kvarn_testing::ServerBuilder::from(extensions).run().await;
334 }
335}