1mod auth;
2mod indexing;
3mod ranges;
4use std::{
5 borrow::Cow,
6 collections::HashMap,
7 fs::{self, File, OpenOptions},
8 io::{self, BufReader, Read, Seek, SeekFrom, Write, stdout},
9 ops::Range,
10 path::Path,
11 sync::Mutex,
12};
13
14pub use auth::AuthConfig;
15use http::HttpMethod;
16use mime::Mime;
17
18use self::{indexing::index_of, ranges::get_range_for};
19use crate::{
20 Result,
21 log::{self, LogLevel},
22 request::HttpRequest,
23};
24
25pub trait Interceptor: Fn(&mut HttpRequest) + Send + Sync + 'static {}
38impl<T> Interceptor for T where T: Fn(&mut HttpRequest) + Send + Sync + 'static {}
39
40#[derive(Default)]
41struct HandlerMethodAssoc {
42 exact: HashMap<Box<str>, Box<dyn RequestHandler>>,
43 #[cfg(feature = "regex")]
44 regex: Vec<(regexpr::Regex, Box<dyn RequestHandler>)>,
45 def: Option<Box<dyn RequestHandler>>,
46}
47
48pub trait RequestHandler: Send + Sync + 'static {
49 fn handle(&self, req: &mut HttpRequest) -> Result<()>;
54}
55
56impl<T> RequestHandler for T
57where
58 T: Fn(&mut HttpRequest) -> Result<()> + Send + Sync + 'static,
59{
60 fn handle(&self, req: &mut HttpRequest) -> Result<()> {
61 self(req)
62 }
63}
64
65enum UrlMatcherInner {
66 Literal(Box<str>),
67 #[cfg(feature = "regex")]
68 Regex(regexpr::Regex),
69}
70
71pub struct UrlMatcher(UrlMatcherInner);
72
73impl UrlMatcher {
74 #[cfg(feature = "regex")]
75 pub fn regex(src: &str) -> Result<Self> {
76 let regex = regexpr::Regex::compile(src).map_err(|err| err.to_string())?;
77 Ok(UrlMatcher(UrlMatcherInner::Regex(regex)))
78 }
79 #[must_use]
80 pub fn literal(src: impl Into<Box<str>>) -> Self {
81 UrlMatcher(UrlMatcherInner::Literal(src.into()))
82 }
83}
84
85impl<T> From<T> for UrlMatcher
86where
87 T: Into<Box<str>>,
88{
89 fn from(value: T) -> Self {
90 Self::literal(value)
91 }
92}
93
94pub struct Handler {
113 handlers: HashMap<HttpMethod, HandlerMethodAssoc>,
114 pre_interceptors: Vec<Box<dyn Interceptor>>,
115 post_interceptors: Vec<Box<dyn Interceptor>>,
116}
117
118impl Handler {
119 #[must_use]
120 pub fn new() -> Self {
121 Self {
122 handlers: HashMap::new(),
123 pre_interceptors: Vec::new(),
124 post_interceptors: Vec::new(),
125 }
126 }
127 #[inline]
129 pub fn get(&mut self, url: impl Into<UrlMatcher>, f: impl RequestHandler) {
130 self.add(HttpMethod::GET, url, f);
131 }
132 #[inline]
134 pub fn post(&mut self, url: impl Into<UrlMatcher>, f: impl RequestHandler) {
135 self.add(HttpMethod::POST, url, f);
136 }
137 #[inline]
139 pub fn delete(&mut self, url: impl Into<UrlMatcher>, f: impl RequestHandler) {
140 self.add(HttpMethod::DELETE, url, f);
141 }
142 #[inline]
144 pub fn head(&mut self, url: impl Into<UrlMatcher>, f: impl RequestHandler) {
145 self.add(HttpMethod::HEAD, url, f);
146 }
147 pub fn add(&mut self, method: HttpMethod, url: impl Into<UrlMatcher>, f: impl RequestHandler) {
154 let map = self.handlers.entry(method).or_default();
155 match url.into().0 {
156 UrlMatcherInner::Literal(lit) => {
157 map.exact.insert(lit, Box::new(f));
158 }
159 #[cfg(feature = "regex")]
160 UrlMatcherInner::Regex(regex) => {
161 map.regex.push((regex, Box::new(f)));
162 }
163 }
164 }
165 #[inline]
171 pub fn add_default(&mut self, method: HttpMethod, f: impl RequestHandler) {
172 self.handlers.entry(method).or_default().def = Some(Box::new(f));
173 }
174 #[inline]
176 pub fn pre_interceptor(&mut self, f: impl Interceptor) {
177 self.pre_interceptors.push(Box::new(f));
178 }
179 #[inline]
181 pub fn post_interceptor(&mut self, f: impl Interceptor) {
182 self.post_interceptors.push(Box::new(f));
183 }
184 #[must_use]
186 pub fn get_handler(&self, method: &HttpMethod, url: &str) -> Option<&dyn RequestHandler> {
187 let handler_table = self.handlers.get(method)?;
188
189 let handler = handler_table.exact.get(url);
190
191 #[cfg(feature = "regex")]
192 let handler = handler.or_else(|| {
193 handler_table
194 .regex
195 .iter()
196 .find(|(r, _)| r.test(url))
197 .map(|(_, f)| f)
198 });
199
200 handler.or(handler_table.def.as_ref()).map(|b| &**b)
201 }
202 pub fn handle(&self, req: &mut HttpRequest) -> Result<()> {
205 self.pre_interceptors.iter().for_each(|f| f(req));
206 let result = match self.get_handler(req.method(), req.url()) {
207 Some(handler) => handler.handle(req).or_else(|err| {
208 eprintln!("ERROR: {err}");
209 req.server_error()
210 }),
211 None => req.forbidden(),
212 };
213 self.post_interceptors.iter().for_each(|f| f(req));
214 result
215 }
216}
217
218impl Default for Handler {
219 fn default() -> Self {
238 let mut handler = Self::new();
239 handler.pre_interceptor(suffix_html);
240 handler.pre_interceptor(|req| {
241 req.set_header("Accept-Ranges", "bytes");
242 });
243
244 if log::get_level() >= LogLevel::Info {
245 handler.post_interceptor(log_stdout);
246 }
247 handler
248 }
249}
250
251fn head_headers(req: &mut HttpRequest) -> Result<Option<Range<u64>>> {
252 let filename = req.filename()?;
253 if dir_exists(&filename) {
254 req.set_header("Content-Type", "text/html");
255 return Ok(None);
256 }
257 match File::open(&*filename) {
258 Ok(file) => {
259 if let Ok(mime) = Mime::from_filename(&filename) {
260 req.set_header("Content-Type", mime.to_string());
261 }
262 let metadata = file.metadata()?;
263 let len = metadata.len();
264 if metadata.is_file() {
265 req.set_header("Content-Length", len.to_string());
266 }
267 let Some(range) = req.header("Range") else {
268 return Ok(None);
269 };
270 let range = get_range_for(range, len)?;
271 if range.end > len || range.end <= range.start {
272 req.set_status(416);
273 } else {
274 req.set_status(206);
275 req.set_header("Content-Length", (range.end - range.start).to_string());
276 req.set_header(
277 "Content-Range",
278 format!("bytes {}-{}/{}", range.start, range.end - 1, len),
279 );
280 }
281 return Ok(Some(range));
282 }
283 Err(err) => {
284 let status = match err.kind() {
285 io::ErrorKind::PermissionDenied => 403,
286 _ => 404,
287 };
288 req.set_status(status);
289 }
290 }
291 Ok(None)
292}
293
294#[inline]
295fn show_hidden(req: &HttpRequest) -> bool {
296 match req.param("hidden") {
297 Some(s) => s != "false",
298 None => true,
299 }
300}
301
302pub fn head_handler(req: &mut HttpRequest) -> Result<()> {
305 head_headers(req)?;
306 let filename = req.filename()?;
307 let len = if req.is_http_err() {
308 req.error_page().len()
309 } else if dir_exists(&filename) {
310 index_of(&filename, show_hidden(req))?.len()
311 } else {
312 0
313 };
314
315 if len > 0 {
316 req.set_header("Content-Length", len.to_string());
317 }
318 req.respond()
319}
320
321pub fn cat_handler(req: &mut HttpRequest) -> Result<()> {
326 let range = head_headers(req)?;
327 if req.is_http_err() {
328 return req.respond_error_page();
329 }
330 let filename = req.filename()?;
331 if dir_exists(&filename) {
332 let page = index_of(&filename, show_hidden(req))?;
333 return req.respond_str(&page);
334 }
335 let mut file = File::open(&*req.filename()?)?;
336 if let Some(range) = range {
337 file.seek(SeekFrom::Start(range.start))?;
338 let mut reader = BufReader::new(file).take(range.end - range.start);
339 req.respond_reader(&mut reader)
340 } else {
341 let mut reader = BufReader::new(file);
342 req.respond_reader(&mut reader)
343 }
344}
345
346pub fn post_handler(req: &mut HttpRequest) -> Result<()> {
351 let filename = req.filename()?;
352 match File::create(&*filename) {
353 Ok(mut file) => {
354 req.read_body(&mut file)?;
355 req.ok()
356 }
357 Err(err) => {
358 println!("Error opening {}: {err}", &filename);
359 match err.kind() {
360 io::ErrorKind::PermissionDenied => req.forbidden(),
361 _ => req.not_found(),
362 }
363 }
364 }
365}
366
367pub fn delete_handler(req: &mut HttpRequest) -> Result<()> {
372 match fs::remove_file(&*req.filename()?) {
373 Ok(()) => req.ok(),
374 Err(err) => match err.kind() {
375 io::ErrorKind::PermissionDenied => req.forbidden(),
376 _ => req.not_found(),
377 },
378 }
379}
380
381#[inline]
382fn file_exists(filename: &str) -> bool {
383 Path::new(filename).is_file()
384}
385
386#[inline]
387fn dir_exists(filename: &str) -> bool {
388 Path::new(filename).is_dir()
389}
390
391pub fn suffix_html(req: &mut HttpRequest) {
397 if file_exists(&req.url()[1..]) {
398 return;
399 }
400 for suffix in [".html", ".php"] {
401 let mut filename = req.url().to_owned();
402 filename.push_str(suffix);
403 if file_exists(&filename[1..]) {
404 req.set_url(filename);
405 break;
406 }
407 }
408}
409
410macro_rules! log {
411 ($w:expr , $req:expr) => {
412 writeln!(
413 $w,
414 "{} {} {} {}",
415 $req.method(),
416 $req.url(),
417 $req.status(),
418 $req.status_msg()
419 )
420 .unwrap();
421 };
422}
423
424pub fn log_stdout(req: &mut HttpRequest) {
426 log!(&mut stdout(), req);
427}
428
429#[allow(clippy::missing_panics_doc)]
436pub fn log_file(filename: &str) -> crate::Result<Box<dyn Interceptor>> {
437 let file = OpenOptions::new()
438 .append(true)
439 .create(true)
440 .open(filename)
441 .map_err(|err| Cow::Owned(format!("Error creating file: {filename}: {err}")))?;
442 let file = Mutex::new(file);
443 Ok(Box::new(move |req: &mut HttpRequest| {
444 #[allow(clippy::unwrap_used)]
445 let mut file = file.lock().unwrap();
446 log!(&mut *file, req);
447 }))
448}
449
450pub fn root_handler(req: &mut HttpRequest) -> Result<()> {
455 if file_exists("index.html") {
457 req.set_url("/index.html");
458 } else if file_exists("index.php") {
459 file_exists("index.php");
460 }
461 if *req.method() == HttpMethod::GET {
462 cat_handler(req)
463 } else {
464 head_handler(req)
465 }
466}
467
468pub fn redirect(uri: impl Into<Box<str>>) -> impl RequestHandler {
469 let uri = uri.into();
470 move |req: &mut HttpRequest| {
471 req.set_header("Location", &*uri);
472 req.set_header("Content-Length", "0");
473 req.set_status(308).respond()
474 }
475}
476
477pub mod presets {
478 use http::HttpMethod;
479
480 use crate::handler::Handler;
481
482 pub fn read() -> Handler {
483 let mut h = Handler::default();
484 h.add_default(HttpMethod::GET, super::cat_handler);
485 h.add_default(HttpMethod::HEAD, super::head_handler);
486 h.get("/", super::root_handler);
487 h.head("/", super::root_handler);
488 h
489 }
490
491 pub fn read_write() -> Handler {
492 let mut h = read();
493 h.add_default(HttpMethod::POST, super::post_handler);
494 h.add_default(HttpMethod::DELETE, super::delete_handler);
495 h
496 }
497
498 pub fn post() -> Handler {
499 let mut h = Handler::default();
500 h.add_default(HttpMethod::POST, super::post_handler);
501 h
502 }
503}