@@ -102,6 +102,17 @@ async fn get_or_create_semaphore(locks: &FileSemaphores, filename: &str) -> Arc<
102102 . clone ( )
103103}
104104
105+ /// Remove semaphore entries that are no longer in use (strong_count == 1 means
106+ /// only the map itself holds a reference).
107+ async fn cleanup_semaphore ( locks : & FileSemaphores , filename : & str ) {
108+ let mut map = locks. write ( ) . await ;
109+ if let Some ( sem) = map. get ( filename) {
110+ if Arc :: strong_count ( sem) == 1 {
111+ map. remove ( filename) ;
112+ }
113+ }
114+ }
115+
105116struct ReceivedFile {
106117 original_filename : String ,
107118 cached_filename : String ,
@@ -491,6 +502,13 @@ prefixes = ["/bob"]
491502prefixes = [""]
492503*/
493504
505+ fn validate_path ( path : & str ) -> Result < ( ) , String > {
506+ if path. contains ( ".." ) {
507+ return Err ( "Path traversal detected" . to_string ( ) ) ;
508+ }
509+ Ok ( ( ) )
510+ }
511+
494512fn verify_upload_permissions ( owner : & str , path : & str ) -> Result < ( ) , String > {
495513 let cfg_content = get_config_content ( ) ;
496514 let cfg: Table = toml:: from_str ( & cfg_content) . unwrap ( ) ;
@@ -559,9 +577,19 @@ async fn ax_post_file(
559577 let mut file0_filename: String = "" . to_string ( ) ;
560578 let mut upload_result: Option < ( StatusCode , Vec < u8 > ) > = None ;
561579 let mut buffered_file: Option < tempfile:: NamedTempFile > = None ;
580+ let mut locked_path: Option < String > = None ;
562581
563- while let Some ( field) = multipart. next_field ( ) . await . unwrap ( ) {
564- let name = field. name ( ) . unwrap ( ) . to_string ( ) ;
582+ while let Some ( field) = match multipart. next_field ( ) . await {
583+ Ok ( field) => field,
584+ Err ( e) => {
585+ eprintln ! ( "Error reading multipart field: {:?}" , e) ;
586+ return ( StatusCode :: BAD_REQUEST , b"Malformed multipart request" . to_vec ( ) ) ;
587+ }
588+ } {
589+ let name = match field. name ( ) {
590+ Some ( name) => name. to_string ( ) ,
591+ None => continue ,
592+ } ;
565593 let filename = field. file_name ( ) . map ( |f| f. to_string ( ) ) ;
566594
567595 if name == "path" {
@@ -658,6 +686,15 @@ async fn ax_post_file(
658686
659687 let full_path = format ! ( "{}/{}" , path, file0_filename) ;
660688
689+ // validate path for traversal
690+ match validate_path ( & full_path) {
691+ Ok ( _) => ( ) ,
692+ Err ( e) => {
693+ upload_result = Some ( ( StatusCode :: BAD_REQUEST , e. into_bytes ( ) ) ) ;
694+ break ;
695+ }
696+ }
697+
661698 // verify upload permissions
662699 match verify_upload_permissions ( & owner, & path) {
663700 Ok ( _) => ( ) ,
@@ -669,6 +706,7 @@ async fn ax_post_file(
669706
670707 let hdr_content_type = headers. get ( "Content-Type-Upstream" ) ;
671708 let semaphore = get_or_create_semaphore ( & state. file_locks , & full_path) . await ;
709+ locked_path = Some ( full_path. clone ( ) ) ;
672710
673711 // Try to acquire permit - wait for up to 30 seconds
674712 let _permit = match tokio:: time:: timeout (
@@ -764,6 +802,11 @@ async fn ax_post_file(
764802 }
765803 }
766804
805+ // Clean up semaphore from the fast path (permit already dropped by leaving the loop)
806+ if let Some ( ref lp) = locked_path {
807+ cleanup_semaphore ( & state. file_locks , lp) . await ;
808+ }
809+
767810 // Handle buffered file0 case (file0 arrived before path)
768811 if upload_result. is_none ( ) {
769812 if let Some ( tmp) = buffered_file {
@@ -781,6 +824,13 @@ async fn ax_post_file(
781824
782825 let full_path = format ! ( "{}/{}" , path, file0_filename) ;
783826
827+ match validate_path ( & full_path) {
828+ Ok ( _) => ( ) ,
829+ Err ( e) => {
830+ return ( StatusCode :: BAD_REQUEST , e. into_bytes ( ) ) ;
831+ }
832+ }
833+
784834 match verify_upload_permissions ( & owner, & path) {
785835 Ok ( _) => ( ) ,
786836 Err ( e) => {
@@ -839,6 +889,9 @@ async fn ax_post_file(
839889
840890 // tmp (NamedTempFile) is dropped here, auto-deleting the temp file
841891
892+ drop ( _permit) ;
893+ cleanup_semaphore ( & state. file_locks , & full_path) . await ;
894+
842895 if result. is_empty ( ) {
843896 return ( StatusCode :: CONFLICT , Vec :: new ( ) ) ;
844897 }
@@ -926,6 +979,11 @@ async fn ax_get_file(
926979
927980 // IMPORTANT! Headers in cache must be stored in lowercase
928981 let received_file = driver_get_file ( filepath. clone ( ) ) ;
982+
983+ // Release the semaphore now that the file is resolved
984+ drop ( _permit) ;
985+ cleanup_semaphore ( & state. file_locks , & filepath) . await ;
986+
929987 if !received_file. valid {
930988 println ! (
931989 "{:?} 404 0 {} {} {} {}" ,
@@ -997,7 +1055,9 @@ async fn ax_get_file(
9971055
9981056 /* Usually HEAD is used to check if the file exists and range is supported */
9991057 if method == axum:: http:: Method :: HEAD {
1000- //println!("HEAD request, returning headers only");
1058+ if let Ok ( val) = header:: HeaderValue :: from_str ( & metadata. len ( ) . to_string ( ) ) {
1059+ headers. insert ( header:: CONTENT_LENGTH , val) ;
1060+ }
10011061 println ! (
10021062 "{:?} 200 0 {} {} {} {}" ,
10031063 remote_addr, human_time, method, filepath, user_agent_str
@@ -1011,7 +1071,12 @@ async fn ax_get_file(
10111071 let mut end = metadata. len ( ) ;
10121072 // is Content-Range present?
10131073 if let Some ( range) = rxheaders. get ( "Range" ) {
1014- ( start, end) = parse_range ( range. to_str ( ) . unwrap ( ) ) ;
1074+ match range. to_str ( ) . ok ( ) . and_then ( parse_range) {
1075+ Some ( parsed) => ( start, end) = parsed,
1076+ None => {
1077+ return ( StatusCode :: RANGE_NOT_SATISFIABLE , "Malformed Range header" ) . into_response ( ) ;
1078+ }
1079+ }
10151080 }
10161081 // if start is set to non-zero, we need to seek
10171082 if start != 0 && ( end == metadata. len ( ) || end == 0 ) {
@@ -1096,29 +1161,29 @@ fn write_file_driver(
10961161
10971162/// Parse range header
10981163/// We support limited range only for now
1099- fn parse_range ( range : & str ) -> ( u64 , u64 ) {
1100- let parts: Vec < & str > = range. split ( "=" ) . collect ( ) ;
1101- let range_parts: Vec < & str > = parts[ 1 ] . split ( "-" ) . collect ( ) ;
1102- let start = range_parts[ 0 ] . parse :: < u64 > ( ) . unwrap ( ) ;
1103- if range_parts. len ( ) == 1 {
1104- return ( start, 0 ) ;
1105- }
1106- let end = range_parts[ 1 ] . parse :: < u64 > ( ) ;
1107- match end {
1108- Ok ( end) => ( start, end) ,
1109- Err ( _) => ( start, 0 ) ,
1110- }
1164+ fn parse_range ( range : & str ) -> Option < ( u64 , u64 ) > {
1165+ let ( _, range_spec) = range. split_once ( '=' ) ?;
1166+ let ( start_str, end_str) = range_spec. split_once ( '-' ) ?;
1167+ let start = start_str. parse :: < u64 > ( ) . ok ( ) ?;
1168+ let end = end_str. parse :: < u64 > ( ) . unwrap_or ( 0 ) ;
1169+ Some ( ( start, end) )
11111170}
11121171
11131172/// Verify the Authorization header
11141173/// Return error message + owner if the token is correct
11151174fn verify_auth_hdr ( headers : & HeaderMap ) -> Result < String , Option < String > > {
1116- let auth = headers. get ( "Authorization" ) ;
1117- if auth == None {
1175+ let auth = match headers. get ( "Authorization" ) {
1176+ Some ( auth) => auth,
1177+ None => return Err ( None ) ,
1178+ } ;
1179+ let auth_str = match auth. to_str ( ) {
1180+ Ok ( s) => s,
1181+ Err ( _) => return Err ( None ) ,
1182+ } ;
1183+ let token_parts: Vec < & str > = auth_str. split_whitespace ( ) . collect ( ) ;
1184+ if token_parts. is_empty ( ) {
11181185 return Err ( None ) ;
11191186 }
1120- let token = auth. unwrap ( ) . to_str ( ) . unwrap ( ) . split_whitespace ( ) ;
1121- let token_parts: Vec < & str > = token. collect ( ) ;
11221187 if token_parts. len ( ) != 2 {
11231188 let verif_result = storjwt:: verify_jwt_token ( token_parts[ 0 ] ) ;
11241189 let bmap = match verif_result {
0 commit comments