zip_static_handler 0.22.0

Static file handler from zip archive
Documentation
use crate::handler::{Entry, Handler};
use crate::http::headers::{Line, LOCATION};
use crate::http::response::StatusCode;
use min_http11_parser::error::Error;
use min_http11_parser::method::Method;
use min_http11_parser::parser::{BodyEncoding, Parser};
use tokio::io::{AsyncBufRead, AsyncWrite, AsyncWriteExt};

pub struct Accepted<'a>(&'a Entry);

impl Handler {
    pub async fn read_request_line<'a, R: AsyncBufRead + Unpin, W: AsyncWrite + Unpin>(
        &self,
        parser: &Parser,
        reader: &mut R,
        writer: &mut W,
        buffer: &'a mut Vec<u8>,
    ) -> Option<(Method, &'a [u8])> {
        match parser.parse_request_line(reader, buffer).await {
            Err(Error::ReadTimeout) => None,
            Err(Error::UnsupportedVersion(_)) => None,
            Err(Error::UnexpectedEndOfFile) => None,
            Err(Error::RequestTooLarge) => {
                Self::write_status_line(writer, StatusCode::RequestTooLarge).await?;
                Self::write_headers(writer, self.error_headers.iter(), true).await?;
                None
            }
            Err(Error::UnknownMethod(_)) => None,
            Err(_) => {
                Self::write_status_line(writer, StatusCode::BadRequest).await?;
                Self::write_headers(writer, self.error_headers.iter(), true).await?;
                None
            }
            Ok(it) => Some(it),
        }
    }

    pub fn accept(&self, path: &str) -> Option<Accepted<'_>> {
        self.paths.get(path).map(Accepted)
    }

    pub async fn handle_not_found<R: AsyncBufRead + Unpin, W: AsyncWrite + Unpin>(
        &self,
        method: &Method,
        parser: &Parser,
        reader: &mut R,
        writer: &mut W,
        buffer: &mut Vec<u8>,
    ) -> Option<()> {
        match method {
            Method::Head | Method::Get => {}
            _ => {
                Self::write_status_line(writer, StatusCode::BadRequest).await?;
                self.write_error_headers(writer, true).await?;
                return None;
            }
        }
        let known_headers = match parser.parse_headers(reader, buffer).await {
            Err(Error::ReadTimeout) => return None,
            Err(Error::RequestTooLarge) => {
                Self::write_status_line(writer, StatusCode::RequestTooLarge).await?;
                self.write_error_headers(writer, true).await?;
                return None;
            }
            Err(_) => {
                Self::write_status_line(writer, StatusCode::BadRequest).await?;
                self.write_error_headers(writer, true).await?;
                return None;
            }
            Ok((known_headers, _)) => known_headers,
        };
        if let Some(value) = known_headers.content_length
            && value != b"0"
        {
            Self::write_status_line(writer, StatusCode::BadRequest).await?;
            self.write_error_headers(writer, true).await?;
            return None;
        }
        Self::write_status_line(writer, StatusCode::NotFound).await?;
        self.write_error_headers(writer, true).await?;
        Some(())
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn handle_path<R: AsyncBufRead + Unpin, W: AsyncWrite + Unpin>(
        &self,
        method: &Method,
        accepted: Accepted<'_>,
        parser: &Parser,
        reader: &mut R,
        writer: &mut W,
        buffer1: &mut Vec<u8>,
        buffer2: &mut Vec<u8>,
    ) -> Option<()> {
        let entry = accepted.0;
        match method {
            Method::Head | Method::Get => {}
            _ => {
                Self::write_status_line(writer, StatusCode::BadRequest).await?;
                self.write_error_headers(writer, true).await?;
                return None;
            }
        }
        let known_headers = match parser.parse_headers(reader, buffer1).await {
            Err(Error::ReadTimeout) => return None,
            Err(Error::RequestTooLarge) => {
                Self::write_status_line(writer, StatusCode::RequestTooLarge).await?;
                self.write_error_headers(writer, true).await?;
                return None;
            }
            Err(_) => {
                Self::write_status_line(writer, StatusCode::BadRequest).await?;
                self.write_error_headers(writer, true).await?;
                return None;
            }
            Ok((known_headers, _)) => known_headers,
        };
        let is_get = match method {
            Method::Get => true,
            Method::Head => false,
            _ => {
                Self::write_status_line(writer, StatusCode::BadRequest).await?;
                self.write_error_headers(writer, true).await?;
                return None;
            }
        };
        match parser.body_encoding(&known_headers) {
            Ok(BodyEncoding::Identity { content_length: 0 }) => {}
            Ok(encoding @ BodyEncoding::Chunked) => {
                match parser.parse_body(reader, buffer2, encoding).await {
                    Ok([]) => {}
                    _ => {
                        Self::write_status_line(writer, StatusCode::BadRequest).await?;
                        self.write_error_headers(writer, true).await?;
                        return None;
                    }
                }
            }
            _ => {
                Self::write_status_line(writer, StatusCode::BadRequest).await?;
                self.write_error_headers(writer, true).await?;
            }
        }
        let headers = &entry.headers;
        if entry.etag.is_some() {
            let etag = entry.etag.as_ref().map(|it| it.as_bytes());
            let none_match = known_headers.if_none_match;
            let if_match = known_headers.if_match;
            if none_match.is_some() && none_match == etag {
                Self::write_status_line(writer, StatusCode::NotModified).await?;
                Self::write_headers(writer, headers.iter(), false).await?;
            } else if if_match.is_some() && if_match != etag {
                Self::write_status_line(writer, StatusCode::PreconditionFailed).await?;
                Self::write_headers(writer, headers.iter(), false).await?;
            } else if let Some(ref body) = entry.content {
                Self::write_status_line(writer, StatusCode::OK).await?;
                Self::write_headers(writer, headers.iter(), false).await?;
                if is_get {
                    Self::write_body(writer, body).await?;
                }
            } else if headers.iter().any(|it| it.key == LOCATION) {
                Self::write_status_line(writer, StatusCode::TemporaryRedirect).await?;
                Self::write_headers(writer, headers.iter(), false).await?;
            } else {
                Self::write_status_line(writer, StatusCode::NoContent).await?;
                Self::write_headers(writer, headers.iter(), false).await?;
            }
        } else {
            Self::write_status_line(writer, StatusCode::PermanentRedirect).await?;
            Self::write_headers(writer, headers.iter(), false).await?;
        }
        Some(())
    }

    pub async fn write_status_line<T: AsyncWrite + Unpin>(
        writer: &mut T,
        code: StatusCode,
    ) -> Option<()> {
        writer
            .write_all(match code {
                StatusCode::OK => b"HTTP/1.1 200 OK\r\n",
                StatusCode::NoContent => b"HTTP/1.1 204 No Content\r\n",
                StatusCode::NotModified => b"HTTP/1.1 304 Not Modified\r\n",
                StatusCode::TemporaryRedirect => b"HTTP/1.1 307 Temporary Redirect\r\n",
                StatusCode::PermanentRedirect => b"HTTP/1.1 308 Permanent Redirect\r\n",
                StatusCode::BadRequest => b"HTTP/1.1 400 Bad Request\r\n",
                StatusCode::Unauthorized => b"HTTP/1.1 401 Unauthorized\r\n",
                StatusCode::Forbidden => b"HTTP/1.1 403 Forbidden\r\n",
                StatusCode::NotFound => b"HTTP/1.1 404 Not Found\r\n",
                StatusCode::MethodNotAllowed => b"HTTP/1.1 405 Method Not Allowed\r\n",
                StatusCode::RequestTimeout => b"HTTP/1.1 408 Request Timeout\r\n",
                StatusCode::PreconditionFailed => b"HTTP/1.1 412 Precondition Failed\r\n",
                StatusCode::RequestTooLarge => b"HTTP/1.1 413 Request Too Large\r\n",
                StatusCode::TooManyRequests => b"HTTP/1.1 429 Too Many Requests\r\n",
                StatusCode::InternalServerError => b"HTTP/1.1 500 Internal Server Error\r\n",
                StatusCode::NotImplemented => b"HTTP/1.1 501 Not Implemented\r\n",
                StatusCode::BadGateway => b"HTTP/1.1 502 Bad Gateway\r\n",
                StatusCode::ServiceUnavailable => b"HTTP/1.1 503 Service Unavailable\r\n",
                StatusCode::GatewayTimeout => b"HTTP/1.1 504 Gateway Timeout\r\n",
            })
            .await
            .ok()
    }

    pub async fn write_error_headers<T: AsyncWrite + Unpin>(
        &self,
        writer: &mut T,
        close: bool,
    ) -> Option<()> {
        Self::write_headers(writer, self.error_headers.iter(), close).await
    }

    pub async fn write_headers<T: AsyncWrite + Unpin>(
        writer: &mut T,
        headers: impl Iterator<Item = &Line>,
        close: bool,
    ) -> Option<()> {
        for line in headers {
            writer.write_all(line.key).await.ok()?;
            writer.write_all(b": ").await.ok()?;
            writer.write_all(line.value.as_ref()).await.ok()?;
            writer.write_all(b"\r\n").await.ok()?;
        }
        if close {
            writer.write_all(b"connection: close\r\n\r\n").await.ok()
        } else {
            writer
                .write_all(b"connection: keep-alive\r\nkeep-alive: 60\r\n\r\n")
                .await
                .ok()
        }
    }

    pub async fn write_body<T: AsyncWrite + Unpin>(
        writer: &mut T,
        body: impl AsRef<[u8]>,
    ) -> Option<()> {
        writer.write_all(body.as_ref()).await.ok()
    }
}