static_web_server/
error_page.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// This file is part of Static Web Server.
3// See https://static-web-server.net/ for more information
4// Copyright (C) 2019-present Jose Quintana <joseluisq.net>
5
6//! Error page module to compose an HTML page response.
7//!
8
9use headers::{AcceptRanges, ContentLength, ContentType, HeaderMapExt};
10use hyper::{Body, Method, Response, StatusCode, Uri};
11use mime_guess::mime;
12use std::path::Path;
13
14use crate::{helpers, http_ext::MethodExt, Result};
15
16/// It returns a HTTP error response which also handles available `404` or `50x` HTML content.
17pub fn error_response(
18    uri: &Uri,
19    method: &Method,
20    status_code: &StatusCode,
21    page404: &Path,
22    page50x: &Path,
23) -> Result<Response<Body>> {
24    tracing::warn!(
25        method = ?method, uri = ?uri, status = status_code.as_u16(),
26        error = status_code.canonical_reason().unwrap_or_default()
27    );
28
29    // Check for 4xx/50x status codes and handle their corresponding HTML content
30    let mut page_content = String::new();
31    let status_code = match status_code {
32        // 4xx
33        &StatusCode::BAD_REQUEST
34        | &StatusCode::UNAUTHORIZED
35        | &StatusCode::PAYMENT_REQUIRED
36        | &StatusCode::FORBIDDEN
37        | &StatusCode::NOT_FOUND
38        | &StatusCode::METHOD_NOT_ALLOWED
39        | &StatusCode::NOT_ACCEPTABLE
40        | &StatusCode::PROXY_AUTHENTICATION_REQUIRED
41        | &StatusCode::REQUEST_TIMEOUT
42        | &StatusCode::CONFLICT
43        | &StatusCode::GONE
44        | &StatusCode::LENGTH_REQUIRED
45        | &StatusCode::PRECONDITION_FAILED
46        | &StatusCode::PAYLOAD_TOO_LARGE
47        | &StatusCode::URI_TOO_LONG
48        | &StatusCode::UNSUPPORTED_MEDIA_TYPE
49        | &StatusCode::RANGE_NOT_SATISFIABLE
50        | &StatusCode::EXPECTATION_FAILED => {
51            // Extra check for 404 status code and its HTML content
52            if status_code == &StatusCode::NOT_FOUND {
53                if page404.is_file() {
54                    String::from_utf8_lossy(&helpers::read_bytes_default(page404))
55                        .trim()
56                        .clone_into(&mut page_content);
57                } else {
58                    tracing::debug!(
59                        "page404 file path not found or not a regular file: {}",
60                        page404.display()
61                    );
62                }
63            }
64            status_code
65        }
66        // 50x
67        &StatusCode::INTERNAL_SERVER_ERROR
68        | &StatusCode::NOT_IMPLEMENTED
69        | &StatusCode::BAD_GATEWAY
70        | &StatusCode::SERVICE_UNAVAILABLE
71        | &StatusCode::GATEWAY_TIMEOUT
72        | &StatusCode::HTTP_VERSION_NOT_SUPPORTED
73        | &StatusCode::VARIANT_ALSO_NEGOTIATES
74        | &StatusCode::INSUFFICIENT_STORAGE
75        | &StatusCode::LOOP_DETECTED => {
76            // HTML content check for status codes 50x
77            if page50x.is_file() {
78                String::from_utf8_lossy(&helpers::read_bytes_default(page50x))
79                    .trim()
80                    .clone_into(&mut page_content);
81            } else {
82                tracing::debug!(
83                    "page50x file path not found or not a regular file: {}",
84                    page50x.display()
85                );
86            }
87            status_code
88        }
89        // other status codes
90        _ => status_code,
91    };
92
93    if page_content.is_empty() {
94        page_content = [
95            "<html><head><title>",
96            status_code.as_str(),
97            " ",
98            status_code.canonical_reason().unwrap_or_default(),
99            "</title></head><body><center><h1>",
100            status_code.as_str(),
101            " ",
102            status_code.canonical_reason().unwrap_or_default(),
103            "</h1></center></body></html>",
104        ]
105        .concat();
106    }
107
108    let mut body = Body::empty();
109    let len = page_content.len() as u64;
110
111    if !method.is_head() {
112        body = Body::from(page_content)
113    }
114
115    let mut resp = Response::new(body);
116    *resp.status_mut() = *status_code;
117    resp.headers_mut()
118        .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
119    resp.headers_mut().typed_insert(ContentLength(len));
120    resp.headers_mut().typed_insert(AcceptRanges::bytes());
121
122    Ok(resp)
123}