@@ -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,28 +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 = if path. is_empty ( ) {
692- file0_filename. clone ( )
693- } else {
694- format ! ( "{}/{}" , path, file0_filename)
695- } ;
696- // Normalize: strip leading slashes so the path is always relative
697- let full_path = full_path. trim_start_matches ( '/' ) . to_string ( ) ;
698-
699- // validate path for traversal
700- match validate_path ( & full_path) {
701- Ok ( _) => ( ) ,
704+ let full_path = match build_storage_key ( & mut path, & file0_filename) {
705+ Ok ( p) => p,
702706 Err ( e) => {
703707 upload_result = Some ( ( StatusCode :: BAD_REQUEST , e. into_bytes ( ) ) ) ;
704708 break ;
705709 }
706- }
710+ } ;
707711
708712 // verify upload permissions
709713 match verify_upload_permissions ( & owner, & path) {
@@ -820,25 +824,12 @@ async fn ax_post_file(
820824 // Handle buffered file0 case (file0 arrived before path)
821825 if upload_result. is_none ( ) {
822826 if let Some ( tmp) = buffered_file {
823- if path. ends_with ( "/" ) {
824- debug_log ! ( "Removing trailing /, workaround" ) ;
825- path. pop ( ) ;
826- }
827-
828- let full_path = if path. is_empty ( ) {
829- file0_filename. clone ( )
830- } else {
831- format ! ( "{}/{}" , path, file0_filename)
832- } ;
833- // Normalize: strip leading slashes so the path is always relative
834- let full_path = full_path. trim_start_matches ( '/' ) . to_string ( ) ;
835-
836- match validate_path ( & full_path) {
837- Ok ( _) => ( ) ,
827+ let full_path = match build_storage_key ( & mut path, & file0_filename) {
828+ Ok ( p) => p,
838829 Err ( e) => {
839830 return ( StatusCode :: BAD_REQUEST , e. into_bytes ( ) ) ;
840831 }
841- }
832+ } ;
842833
843834 match verify_upload_permissions ( & owner, & path) {
844835 Ok ( _) => ( ) ,
0 commit comments