1use crate::into::IntoResponse;
2use crate::Response;
3
4use tokio::io;
5
6use std::fmt;
7use std::path::{Path, PathBuf};
8use std::str::Utf8Error;
9
10use percent_encoding::percent_decode_str;
11
12mod file;
13pub use file::File;
14
15mod partial_file;
16pub use partial_file::{PartialFile, Range};
17
18mod caching;
19pub use caching::Caching;
20
21mod static_files;
22pub use static_files::{
23 serve_file, StaticFile, StaticFileOwned, StaticFiles, StaticFilesOwned,
24};
25
26mod memory_files;
27pub use memory_files::{serve_memory_file, MemoryFile};
28
29#[macro_export]
51macro_rules! memory_file {
52 ($uri:expr, $path:expr) => {
53 $crate::fs::MemoryFile::new($uri, $path, include_bytes!($path))
54 };
55 ($uri:expr, $path:expr, $duration:expr) => {
56 $crate::fs::MemoryFile::cache_with_age(
57 $uri,
58 $path,
59 include_bytes!($path),
60 $duration,
61 )
62 };
63}
64
65pub(crate) async fn with_file<P>(path: P) -> io::Result<Response>
67where
68 P: AsRef<Path>,
69{
70 File::open(path).await.map(|f| f.into_response())
71}
72
73pub(crate) async fn with_partial_file<P>(
75 path: P,
76 range: Range,
77) -> io::Result<Response>
78where
79 P: AsRef<Path>,
80{
81 PartialFile::open(path, range)
82 .await
83 .map(|pf| pf.into_response())
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum IntoPathBufError {
88 TraversalAttack,
89 InvalidCharacter,
90 Utf8(Utf8Error),
91}
92
93impl fmt::Display for IntoPathBufError {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 fmt::Debug::fmt(self, f)
96 }
97}
98
99impl std::error::Error for IntoPathBufError {
100 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
101 match self {
102 Self::Utf8(u) => Some(u),
103 _ => None,
104 }
105 }
106}
107
108impl From<Utf8Error> for IntoPathBufError {
109 fn from(e: Utf8Error) -> Self {
110 Self::Utf8(e)
111 }
112}
113
114pub trait IntoPathBuf {
115 fn into_path_buf(self) -> Result<PathBuf, IntoPathBufError>;
116}
117
118impl IntoPathBuf for &str {
119 fn into_path_buf(self) -> Result<PathBuf, IntoPathBufError> {
120 let mut path_buf = PathBuf::new();
121
122 for (i, part) in self.split('/').enumerate() {
123 match (i, part) {
124 (0, "") => continue,
125 (_, "..") => {
126 path_buf.pop();
127 }
128 (_, ".") => continue,
129 (_, p) => {
130 let dec = percent_decode_str(p).decode_utf8()?;
131
132 if dec.contains('\\')
133 || dec.contains('/') || dec.starts_with('.')
134 {
135 return Err(IntoPathBufError::InvalidCharacter);
136 }
137
138 path_buf.push(dec.as_ref());
139 }
140 }
141 }
142
143 Ok(path_buf)
144 }
145}