Skip to content

Commit ecb7638

Browse files
authored
Merge pull request #28 from nuclearcat/fix-path
Treat empty path as /
2 parents a2afd70 + 8ea2685 commit ecb7638

2 files changed

Lines changed: 39 additions & 30 deletions

File tree

src/local.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,20 @@ fn ensure_directory_exists(file_path: &Path) -> Result<(), std::io::Error> {
6666
fn get_storage_file_path(filename: &str) -> PathBuf {
6767
let config = get_local_config();
6868
let storage_path = Path::new(&config.storage_path);
69-
storage_path.join(filename)
69+
// Strip leading slashes to ensure the path is always relative to storage root
70+
let safe_name = filename.trim_start_matches('/');
71+
let full = storage_path.join(safe_name);
72+
// Defense-in-depth: verify resolved path stays within storage root
73+
let canonical_storage = storage_path.canonicalize().unwrap_or_else(|_| storage_path.to_path_buf());
74+
// Use the parent directory for canonicalization since the file may not exist yet
75+
let parent = full.parent().unwrap_or(&full);
76+
let canonical_parent = parent.canonicalize().unwrap_or_else(|_| parent.to_path_buf());
77+
if !canonical_parent.starts_with(&canonical_storage) {
78+
// Fall back to storage root to prevent escape
79+
storage_path.join(Path::new(safe_name).file_name().unwrap_or_default())
80+
} else {
81+
full
82+
}
7083
}
7184

7285
/// Get metadata file path for storing headers

src/main.rs

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,25 @@ fn validate_path(path: &str) -> Result<(), String> {
513513
Ok(())
514514
}
515515

516+
/// Build a normalized, validated storage key from the upload path and filename.
517+
/// Strips trailing slashes from `path`, joins with `filename`, strips leading slashes,
518+
/// and validates against path traversal.
519+
fn build_storage_key(path: &mut String, filename: &str) -> Result<String, String> {
520+
// Remove trailing slash
521+
if path.ends_with('/') {
522+
path.pop();
523+
}
524+
let full_path = if path.is_empty() {
525+
filename.to_string()
526+
} else {
527+
format!("{}/{}", path, filename)
528+
};
529+
// Normalize: strip leading slashes so the path is always relative
530+
let full_path = full_path.trim_start_matches('/').to_string();
531+
validate_path(&full_path)?;
532+
Ok(full_path)
533+
}
534+
516535
fn verify_upload_permissions(owner: &str, path: &str) -> Result<(), String> {
517536
let cfg_content = get_config_content();
518537
let cfg: Table = toml::from_str(&cfg_content).unwrap();
@@ -682,22 +701,13 @@ async fn ax_post_file(
682701
}
683702

684703
// FAST PATH: path already set, stream directly
685-
// if path ends on /, remove it
686-
if path.ends_with("/") {
687-
debug_log!("Removing trailing /, workaround");
688-
path.pop();
689-
}
690-
691-
let full_path = format!("{}/{}", path, file0_filename);
692-
693-
// validate path for traversal
694-
match validate_path(&full_path) {
695-
Ok(_) => (),
704+
let full_path = match build_storage_key(&mut path, &file0_filename) {
705+
Ok(p) => p,
696706
Err(e) => {
697707
upload_result = Some((StatusCode::BAD_REQUEST, e.into_bytes()));
698708
break;
699709
}
700-
}
710+
};
701711

702712
// verify upload permissions
703713
match verify_upload_permissions(&owner, &path) {
@@ -814,26 +824,12 @@ async fn ax_post_file(
814824
// Handle buffered file0 case (file0 arrived before path)
815825
if upload_result.is_none() {
816826
if let Some(tmp) = buffered_file {
817-
if path.is_empty() {
818-
return (
819-
StatusCode::BAD_REQUEST,
820-
b"Missing path field in upload".to_vec(),
821-
);
822-
}
823-
824-
if path.ends_with("/") {
825-
debug_log!("Removing trailing /, workaround");
826-
path.pop();
827-
}
828-
829-
let full_path = format!("{}/{}", path, file0_filename);
830-
831-
match validate_path(&full_path) {
832-
Ok(_) => (),
827+
let full_path = match build_storage_key(&mut path, &file0_filename) {
828+
Ok(p) => p,
833829
Err(e) => {
834830
return (StatusCode::BAD_REQUEST, e.into_bytes());
835831
}
836-
}
832+
};
837833

838834
match verify_upload_permissions(&owner, &path) {
839835
Ok(_) => (),

0 commit comments

Comments
 (0)