1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// SPDX-License-Identifier: MIT OR Apache-2.0
// This file is part of Static Web Server.
// See https://static-web-server.net/ for more information
// Copyright (C) 2019-present Jose Quintana <joseluisq.net>

//! Compression static module to serve compressed files directly from the file system.
//!

use headers::{ContentCoding, HeaderMap, HeaderValue};
use std::{
    ffi::OsStr,
    fs::Metadata,
    path::{Path, PathBuf},
};

use crate::{compression, static_files::file_metadata};

/// It defines the pre-compressed file variant metadata of a particular file path.
pub struct CompressedFileVariant<'a> {
    /// Current file path.
    pub file_path: PathBuf,
    /// The metadata of the current file.
    pub metadata: Metadata,
    /// The file extension.
    pub extension: &'a str,
}

/// Search for the pre-compressed variant of the given file path.
pub async fn precompressed_variant<'a>(
    file_path: &Path,
    headers: &'a HeaderMap<HeaderValue>,
) -> Option<CompressedFileVariant<'a>> {
    tracing::trace!(
        "preparing pre-compressed file variant path of {}",
        file_path.display()
    );

    // Determine prefered-encoding extension if available
    let comp_ext = match compression::get_prefered_encoding(headers) {
        // https://zlib.net/zlib_faq.html#faq39
        #[cfg(feature = "compression-gzip")]
        Some(ContentCoding::GZIP | ContentCoding::DEFLATE) => "gz",
        // https://peazip.github.io/brotli-compressed-file-format.html
        #[cfg(feature = "compression-brotli")]
        Some(ContentCoding::BROTLI) => "br",
        // https://datatracker.ietf.org/doc/html/rfc8878
        #[cfg(feature = "compression-zstd")]
        Some(ContentCoding::ZSTD) => "zst",
        _ => {
            tracing::trace!(
                "preferred encoding based on the file extension was not determined, skipping"
            );
            return None;
        }
    };

    let comp_name = match file_path.file_name().and_then(OsStr::to_str) {
        Some(v) => v,
        None => {
            tracing::trace!("file name was not determined for the current path, skipping");
            return None;
        }
    };

    let file_path = file_path.with_file_name([comp_name, ".", comp_ext].concat());
    tracing::trace!(
        "trying to get the pre-compressed file variant metadata for {}",
        file_path.display()
    );

    let (metadata, is_dir) = match file_metadata(&file_path) {
        Ok(v) => v,
        Err(e) => {
            tracing::trace!("pre-compressed file variant error: {:?}", e);
            return None;
        }
    };

    if is_dir {
        tracing::trace!("pre-compressed file variant found but it's a directory, skipping");
        return None;
    }

    tracing::trace!("pre-compressed file variant found, serving it directly");

    Some(CompressedFileVariant {
        file_path,
        metadata,
        extension: if comp_ext == "gz" { "gzip" } else { comp_ext },
    })
}