fire_http/fs/
mod.rs

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/// Static get handler which servers/returns a file which gets loaded into
30/// the binary at compile time.
31///
32/// ## Example
33/// ```
34/// # use fire_http as fire;
35/// use std::time::Duration;
36/// use fire::fs::MemoryFile;
37/// use fire::memory_file;
38///
39/// const INDEX: MemoryFile = memory_file!(
40/// 	"/",
41/// 	"../../examples/www/hello_world.html"
42/// );
43///
44/// const INDEX_WITH_CACHE: MemoryFile = memory_file!(
45/// 	"/",
46/// 	"../../examples/www/hello_world.html",
47/// 	Duration::from_secs(10)
48/// );
49/// ```
50#[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
65/// returns io::Error not found if the path is a directory
66pub(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
73/// returns io::Error not found if the path is a directory
74pub(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}