@@ -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+
516535fn 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