use std::path::{Path, PathBuf};
use crate::error::StaticError;
pub fn resolve_with_canonical_root(root_canon: &Path, request_path: &str) -> Result<PathBuf, StaticError> {
if request_path.contains('\0') {
return Err(StaticError::Traversal(request_path.to_string()));
}
let decoded = decode_request_path(request_path);
for segment in decoded.split('/') {
if segment == ".." {
return Err(StaticError::Traversal(request_path.to_string()));
}
}
let stripped = decoded.trim_start_matches('/');
let joined = root_canon.join(stripped);
let canon = joined.canonicalize().map_err(|_| {
StaticError::NotFound(request_path.to_string())
})?;
if !canon.starts_with(root_canon) {
return Err(StaticError::Traversal(request_path.to_string()));
}
if canon.is_dir() {
let index = canon.join("index.html");
let index_canon = index.canonicalize().map_err(|_| {
StaticError::NotFound(request_path.to_string())
})?;
if !index_canon.starts_with(root_canon) {
return Err(StaticError::Traversal(request_path.to_string()));
}
Ok(index)
} else {
Ok(canon)
}
}
pub(crate) fn decode_request_path(request_path: &str) -> String {
percent_encoding::percent_decode_str(request_path)
.decode_utf8()
.map(|s| s.to_string())
.unwrap_or_else(|_| request_path.to_string())
}
pub fn resolve(root: &Path, request_path: &str) -> Result<PathBuf, StaticError> {
let root_canon = root.canonicalize().map_err(StaticError::Io)?;
resolve_with_canonical_root(&root_canon, request_path)
}