http_srv/handler/
mod.rs

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
25/* /// HandlerFunc trait */
26/* /// */
27/* /// Represents a function that handles an [HttpRequest] */
28/* /// It receives a mutable reference to an [HttpRequest] and returns a [Result]<()> */
29/* pub trait HandlerFunc : Fn(&mut HttpRequest) -> Result<()> + Send + Sync + 'static { } */
30/* impl<T> HandlerFunc for T */
31/* where T: */
32
33/// Interceptor trait
34///
35/// Represents a function that "intercepts" a request.
36/// It can change it's state or log it's output.
37pub 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    /// Handler the request
50    ///
51    /// # Errors
52    /// If some error ocurred while processing the request
53    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
94/// Handler
95///
96/// Matches [requests](HttpRequest) by their method and url, and
97/// executes different handler functions for them.
98///
99/// # Example
100/// ```
101/// use http_srv::handler::{self,*};
102/// use http::request::HttpRequest;
103/// use http::HttpMethod;
104///
105/// let mut handler = Handler::new();
106/// handler.get("/", |req: &mut HttpRequest| {
107///     req.respond_str("Hello world! :)")
108/// });
109/// handler.add_default(HttpMethod::GET, handler::cat_handler);
110/// handler.post_interceptor(handler::log_stdout);
111/// ```
112pub 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    /// Shortcut for [add](Handler::add)([`HttpMethod::GET`], ...)
128    #[inline]
129    pub fn get(&mut self, url: impl Into<UrlMatcher>, f: impl RequestHandler) {
130        self.add(HttpMethod::GET, url, f);
131    }
132    /// Shortcut for [add](Handler::add)([`HttpMethod::POST`], ...)
133    #[inline]
134    pub fn post(&mut self, url: impl Into<UrlMatcher>, f: impl RequestHandler) {
135        self.add(HttpMethod::POST, url, f);
136    }
137    /// Shortcut for [add](Handler::add)([`HttpMethod::DELETE`], ...)
138    #[inline]
139    pub fn delete(&mut self, url: impl Into<UrlMatcher>, f: impl RequestHandler) {
140        self.add(HttpMethod::DELETE, url, f);
141    }
142    /// Shortcut for [add](Handler::add)([`HttpMethod::HEAD`], ...)
143    #[inline]
144    pub fn head(&mut self, url: impl Into<UrlMatcher>, f: impl RequestHandler) {
145        self.add(HttpMethod::HEAD, url, f);
146    }
147    /// Adds a handler for a request type
148    ///
149    /// - method: HTTP [method](HttpMethod) to match
150    /// - url: URL for the handler
151    /// - f: [Handler](RequestHandler) for the request
152    ///
153    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    /// Adds a default handler for all requests of a certain type
166    ///
167    /// - method: HTTP [method](HttpMethod) to match
168    /// - f: [Handler](RequestHandler) for the requests
169    ///
170    #[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    /// Add a function to run before the request is processed
175    #[inline]
176    pub fn pre_interceptor(&mut self, f: impl Interceptor) {
177        self.pre_interceptors.push(Box::new(f));
178    }
179    /// Add a function to run after the request is processed
180    #[inline]
181    pub fn post_interceptor(&mut self, f: impl Interceptor) {
182        self.post_interceptors.push(Box::new(f));
183    }
184    /// Get the handler for a certain method and url
185    #[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    /// Handles a request if it finds a [`RequestHandler`] for it.
203    /// Else, it returns a 403 FORBIDDEN response
204    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    /// Default Handler
220    ///
221    /// # Pre Interceptors
222    ///  - [`suffix_html`]
223    ///  - Set Header: "Accept-Ranges: bytes"
224    ///
225    /// # Handler Functions
226    /// - [GET](HttpMethod::GET): [`cat_handler`]
227    /// - [POST](HttpMethod::POST): [`post_handler`]
228    /// - [DELETE](HttpMethod::DELETE): [`delete_handler`]
229    /// - [HEAD](HttpMethod::HEAD): [`head_handler`]
230    ///
231    /// - [GET](HttpMethod::GET) "/": [`root_handler`]
232    /// - [HEAD](HttpMethod::HEAD) "/": [`root_handler`]
233    ///
234    /// # Post Interceptors
235    ///  - [`log_stdout`]
236    ///
237    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        handler.add_default(HttpMethod::GET, cat_handler);
245        handler.add_default(HttpMethod::POST, post_handler);
246        handler.add_default(HttpMethod::DELETE, delete_handler);
247        handler.add_default(HttpMethod::HEAD, head_handler);
248
249        handler.get("/", root_handler);
250        handler.head("/", root_handler);
251
252        if log::get_level() >= LogLevel::Info {
253            handler.post_interceptor(log_stdout);
254        }
255        handler
256    }
257}
258
259fn head_headers(req: &mut HttpRequest) -> Result<Option<Range<u64>>> {
260    let filename = req.filename()?;
261    if dir_exists(&filename) {
262        req.set_header("Content-Type", "text/html");
263        return Ok(None);
264    }
265    match File::open(&*filename) {
266        Ok(file) => {
267            if let Ok(mime) = Mime::from_filename(&filename) {
268                req.set_header("Content-Type", mime.to_string());
269            }
270            let metadata = file.metadata()?;
271            let len = metadata.len();
272            if metadata.is_file() {
273                req.set_header("Content-Length", len.to_string());
274            }
275            let Some(range) = req.header("Range") else {
276                return Ok(None);
277            };
278            let range = get_range_for(range, len)?;
279            if range.end > len || range.end <= range.start {
280                req.set_status(416);
281            } else {
282                req.set_status(206);
283                req.set_header("Content-Length", (range.end - range.start).to_string());
284                req.set_header(
285                    "Content-Range",
286                    format!("bytes {}-{}/{}", range.start, range.end - 1, len),
287                );
288            }
289            return Ok(Some(range));
290        }
291        Err(err) => {
292            let status = match err.kind() {
293                io::ErrorKind::PermissionDenied => 403,
294                _ => 404,
295            };
296            req.set_status(status);
297        }
298    }
299    Ok(None)
300}
301
302#[inline]
303fn show_hidden(req: &HttpRequest) -> bool {
304    match req.param("hidden") {
305        Some(s) => s != "false",
306        None => true,
307    }
308}
309
310/// Returns the headers that would be sent by a [GET](HttpMethod::GET)
311/// [request](HttpRequest), with an empty body.
312pub fn head_handler(req: &mut HttpRequest) -> Result<()> {
313    head_headers(req)?;
314    let filename = req.filename()?;
315    let len = if req.is_http_err() {
316        req.error_page().len()
317    } else if dir_exists(&filename) {
318        index_of(&filename, show_hidden(req))?.len()
319    } else {
320        0
321    };
322
323    if len > 0 {
324        req.set_header("Content-Length", len.to_string());
325    }
326    req.respond()
327}
328
329/// Returns the file, or an index of the directory.
330///
331/// # Errors
332/// If the request returns an Error variant on send
333pub fn cat_handler(req: &mut HttpRequest) -> Result<()> {
334    let range = head_headers(req)?;
335    if req.is_http_err() {
336        return req.respond_error_page();
337    }
338    let filename = req.filename()?;
339    if dir_exists(&filename) {
340        let page = index_of(&filename, show_hidden(req))?;
341        return req.respond_str(&page);
342    }
343    let mut file = File::open(&*req.filename()?)?;
344    if let Some(range) = range {
345        file.seek(SeekFrom::Start(range.start))?;
346        let mut reader = BufReader::new(file).take(range.end - range.start);
347        req.respond_reader(&mut reader)
348    } else {
349        let mut reader = BufReader::new(file);
350        req.respond_reader(&mut reader)
351    }
352}
353
354/// Save the data of the request to the url
355///
356/// # Errors
357/// If the request returns an Error variant on send
358pub fn post_handler(req: &mut HttpRequest) -> Result<()> {
359    let filename = req.filename()?;
360    match File::create(&*filename) {
361        Ok(mut file) => {
362            req.read_body(&mut file)?;
363            req.ok()
364        }
365        Err(err) => {
366            println!("Error opening {}: {err}", &filename);
367            match err.kind() {
368                io::ErrorKind::PermissionDenied => req.forbidden(),
369                _ => req.not_found(),
370            }
371        }
372    }
373}
374
375/// Delete the filename
376///
377/// # Errors
378/// If the request returns an Error variant on send
379pub fn delete_handler(req: &mut HttpRequest) -> Result<()> {
380    match fs::remove_file(&*req.filename()?) {
381        Ok(()) => req.ok(),
382        Err(err) => match err.kind() {
383            io::ErrorKind::PermissionDenied => req.forbidden(),
384            _ => req.not_found(),
385        },
386    }
387}
388
389#[inline]
390fn file_exists(filename: &str) -> bool {
391    Path::new(filename).is_file()
392}
393
394#[inline]
395fn dir_exists(filename: &str) -> bool {
396    Path::new(filename).is_dir()
397}
398
399/// Appends a suffix to the url
400///
401/// If the requested url doesn't exists, try to
402/// append a suffix ('.html', '.php'), and if it
403/// exists, modify the url.
404pub fn suffix_html(req: &mut HttpRequest) {
405    if file_exists(&req.url()[1..]) {
406        return;
407    }
408    for suffix in [".html", ".php"] {
409        let mut filename = req.url().to_owned();
410        filename.push_str(suffix);
411        if file_exists(&filename[1..]) {
412            req.set_url(filename);
413            break;
414        }
415    }
416}
417
418macro_rules! log {
419    ($w:expr , $req:expr) => {
420        writeln!(
421            $w,
422            "{} {} {} {}",
423            $req.method(),
424            $req.url(),
425            $req.status(),
426            $req.status_msg()
427        )
428        .unwrap();
429    };
430}
431
432/// Log the [request](HttpRequest) to stdout
433pub fn log_stdout(req: &mut HttpRequest) {
434    log!(&mut stdout(), req);
435}
436
437/// Log the [request](HttpRequest) to a file
438///
439/// The file is appended to, or created if it doesn't exists
440///
441/// # Errors
442/// If creating the log file fails
443#[allow(clippy::missing_panics_doc)]
444pub fn log_file(filename: &str) -> crate::Result<Box<dyn Interceptor>> {
445    let file = OpenOptions::new()
446        .append(true)
447        .create(true)
448        .open(filename)
449        .map_err(|err| Cow::Owned(format!("Error creating file: {filename}: {err}")))?;
450    let file = Mutex::new(file);
451    Ok(Box::new(move |req: &mut HttpRequest| {
452        #[allow(clippy::unwrap_used)]
453        let mut file = file.lock().unwrap();
454        log!(&mut *file, req);
455    }))
456}
457
458/// Rewrites / to /index.html
459///
460/// # Errors
461/// If the request returns an Error variant on send
462pub fn root_handler(req: &mut HttpRequest) -> Result<()> {
463    if file_exists("index.html") {
464        req.set_url("/index.html");
465    }
466    cat_handler(req)
467}
468
469pub fn redirect(uri: impl Into<Box<str>>) -> impl RequestHandler {
470    let uri = uri.into();
471    move |req: &mut HttpRequest| {
472        req.set_header("Location", &*uri);
473        req.set_header("Content-Length", "0");
474        req.set_status(308).respond()
475    }
476}