@@ -89,6 +89,7 @@ use clap::ArgMatches;
8989use std:: {
9090 env,
9191 error:: Error ,
92+ ffi:: { OsStr , OsString } ,
9293 fmt:: { Debug , Display } ,
9394 path:: { Path , PathBuf } ,
9495} ;
@@ -243,16 +244,21 @@ pub mod arguments {
243244///
244245/// 1. From the '-S' or '--suffix' CLI argument, if present
245246/// 2. From the "SIMPLE_BACKUP_SUFFIX" environment variable, if present
246- /// 3. By using the default '~' if none of the others apply
247+ /// 3. By using the default '~' if none of the others apply, or if they contained slashes
247248///
248249/// This function directly takes [`ArgMatches`] as argument and looks for
249250/// the '-S' and '--suffix' arguments itself.
250251pub fn determine_backup_suffix ( matches : & ArgMatches ) -> String {
251252 let supplied_suffix = matches. get_one :: < String > ( arguments:: OPT_SUFFIX ) ;
252- if let Some ( suffix) = supplied_suffix {
253+ let suffix = if let Some ( suffix) = supplied_suffix {
253254 String :: from ( suffix)
254255 } else {
255256 env:: var ( "SIMPLE_BACKUP_SUFFIX" ) . unwrap_or_else ( |_| DEFAULT_BACKUP_SUFFIX . to_owned ( ) )
257+ } ;
258+ if suffix. contains ( '/' ) {
259+ DEFAULT_BACKUP_SUFFIX . to_owned ( )
260+ } else {
261+ suffix
256262 }
257263}
258264
@@ -413,48 +419,42 @@ fn match_method(method: &str, origin: &str) -> UResult<BackupMode> {
413419 }
414420}
415421
416- pub fn get_backup_path (
422+ pub fn get_backup_path < S : AsRef < OsStr > > (
417423 backup_mode : BackupMode ,
418424 backup_path : & Path ,
419- suffix : & str ,
425+ suffix : S ,
420426) -> Option < PathBuf > {
421427 match backup_mode {
422428 BackupMode :: None => None ,
423- BackupMode :: Simple => Some ( simple_backup_path ( backup_path, suffix) ) ,
429+ BackupMode :: Simple => Some ( simple_backup_path ( backup_path, suffix. as_ref ( ) ) ) ,
424430 BackupMode :: Numbered => Some ( numbered_backup_path ( backup_path) ) ,
425- BackupMode :: Existing => Some ( existing_backup_path ( backup_path, suffix) ) ,
431+ BackupMode :: Existing => Some ( existing_backup_path ( backup_path, suffix. as_ref ( ) ) ) ,
426432 }
427433}
428434
429- fn simple_backup_path ( path : & Path , suffix : & str ) -> PathBuf {
435+ fn simple_backup_path < S : AsRef < OsStr > > ( path : & Path , suffix : S ) -> PathBuf {
430436 let mut file_name = path. file_name ( ) . unwrap_or_default ( ) . to_os_string ( ) ;
431- file_name. push ( suffix) ;
437+ file_name. push ( suffix. as_ref ( ) ) ;
432438 path. with_file_name ( file_name)
433439}
434440
435441fn numbered_backup_path ( path : & Path ) -> PathBuf {
436- let file_name = path. file_name ( ) . unwrap_or_default ( ) ;
437- for i in 1_u64 .. {
438- let mut numbered_file_name = file_name. to_os_string ( ) ;
439- numbered_file_name. push ( format ! ( ".~{i}~" ) ) ;
440- let path = path. with_file_name ( numbered_file_name) ;
441- if !path. exists ( ) {
442- return path;
442+ let mut i: u64 = 1 ;
443+ loop {
444+ let new_path = simple_backup_path ( path, OsString :: from ( format ! ( ".~{i}~" ) ) ) ;
445+ if !new_path. exists ( ) {
446+ return new_path;
443447 }
448+ i += 1 ;
444449 }
445- panic ! ( "cannot create backup" )
446450}
447451
448- fn existing_backup_path ( path : & Path , suffix : & str ) -> PathBuf {
449- let file_name = path. file_name ( ) . unwrap_or_default ( ) ;
450- let mut numbered_file_name = file_name. to_os_string ( ) ;
451- numbered_file_name. push ( ".~1~" ) ;
452- let test_path = path. with_file_name ( numbered_file_name) ;
452+ fn existing_backup_path < S : AsRef < OsStr > > ( path : & Path , suffix : S ) -> PathBuf {
453+ let test_path = simple_backup_path ( path, OsString :: from ( ".~1~" ) ) ;
453454 if test_path. exists ( ) {
454- numbered_backup_path ( path)
455- } else {
456- simple_backup_path ( path, suffix)
455+ return numbered_backup_path ( path) ;
457456 }
457+ simple_backup_path ( path, suffix. as_ref ( ) )
458458}
459459
460460/// Returns true if the source file is likely to be the simple backup file for the target file.
@@ -695,6 +695,16 @@ mod tests {
695695 assert_eq ! ( result, "-v" ) ;
696696 }
697697
698+ #[ test]
699+ fn test_suffix_rejects_path_traversal ( ) {
700+ let _dummy = TEST_MUTEX . lock ( ) . unwrap ( ) ;
701+ let matches =
702+ make_app ( ) . get_matches_from ( vec ! [ "command" , "-b" , "--suffix" , "_/../../dest" ] ) ;
703+
704+ let result = determine_backup_suffix ( & matches) ;
705+ assert_eq ! ( result, DEFAULT_BACKUP_SUFFIX ) ;
706+ }
707+
698708 #[ test]
699709 fn test_numbered_backup_path ( ) {
700710 assert_eq ! ( numbered_backup_path( Path :: new( "" ) ) , PathBuf :: from( ".~1~" ) ) ;
0 commit comments