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
use std::path::{Component, Path, PathBuf};
#[inline]
fn decode_percents(string: &str) -> String {
percent_encoding::percent_decode_str(string)
.decode_utf8_lossy()
.into_owned()
}
fn normalize_path(path: &Path) -> PathBuf {
path.components()
.fold(PathBuf::new(), |mut result, p| match p {
Component::Normal(x) => {
// Parse again to prevent a malicious component containing
// a Windows drive letter, e.g.: `/anypath/c:/windows/win.ini`
if Path::new(&x)
.components()
.all(|c| matches!(c, Component::Normal(_)))
{
result.push(x);
}
result
}
Component::ParentDir => {
result.pop();
result
}
_ => result,
})
}
/// Resolved request path.
pub struct RequestedPath {
/// Sanitized path of the request.
pub sanitized: PathBuf,
/// Whether a directory was requested. (`original` ends with a slash.)
pub is_dir_request: bool,
}
impl RequestedPath {
/// Resolve the requested path to a full filesystem path, limited to the root.
pub fn resolve(request_path: &str) -> Self {
let is_dir_request = request_path.as_bytes().last() == Some(&b'/');
let request_path = PathBuf::from(decode_percents(request_path));
RequestedPath {
sanitized: normalize_path(&request_path),
is_dir_request,
}
}
}