@@ -23,11 +23,11 @@ use crate::path::S3Path;
23
23
use crate :: storage:: S3Storage ;
24
24
use crate :: utils:: { crypto, time, Apply } ;
25
25
26
- use std:: collections:: { HashMap , HashSet , VecDeque } ;
26
+ use std:: collections:: { HashMap , HashSet } ;
27
27
use std:: convert:: TryInto ;
28
28
use std:: env;
29
29
use std:: io:: { self , SeekFrom } ;
30
- use std:: path:: { Component , Path , PathBuf } ;
30
+ use std:: path:: { Path , PathBuf } ;
31
31
32
32
use futures:: io:: { AsyncReadExt , AsyncSeekExt , AsyncWrite , AsyncWriteExt , BufWriter } ;
33
33
use futures:: stream:: { Stream , StreamExt , TryStreamExt } ;
@@ -47,6 +47,75 @@ pub struct FileSystem {
47
47
}
48
48
49
49
impl FileSystem {
50
+ fn url_encode_list_results (
51
+ objects : Vec < Object > ,
52
+ common_prefixes : Vec < String > ,
53
+ ) -> ( Vec < Object > , Vec < String > ) {
54
+ fn encode_with_slash ( s : & str ) -> String {
55
+ s. split ( '/' )
56
+ . map ( |part| urlencoding:: encode ( part) . into_owned ( ) )
57
+ . collect :: < Vec < _ > > ( )
58
+ . join ( "/" )
59
+ }
60
+
61
+ (
62
+ objects
63
+ . into_iter ( )
64
+ . map ( |mut obj| {
65
+ if let Some ( key) = obj. key . as_mut ( ) {
66
+ * key = encode_with_slash ( key) ;
67
+ }
68
+ obj
69
+ } )
70
+ . collect ( ) ,
71
+ common_prefixes
72
+ . into_iter ( )
73
+ . map ( |p| encode_with_slash ( & p) )
74
+ . collect ( ) ,
75
+ )
76
+ }
77
+
78
+ async fn abstract_list_objects (
79
+ & self ,
80
+ bucket : & str ,
81
+ prefix : & Option < String > ,
82
+ delimiter : & Option < String > ,
83
+ max_keys : i64 ,
84
+ ) -> io:: Result < ( Vec < Object > , Vec < String > ) > {
85
+ let bucket_path = self . get_bucket_path ( bucket) ?;
86
+ debug ! ( "Bucket path: {:?}" , bucket_path) ;
87
+
88
+ let ( search_dir, prefix_filter) =
89
+ if delimiter. is_none ( ) || prefix. as_deref ( ) . unwrap_or ( "" ) . is_empty ( ) {
90
+ ( PathBuf :: new ( ) , String :: new ( ) )
91
+ } else {
92
+ self . parse_prefix_and_delimiter ( prefix, delimiter)
93
+ } ;
94
+ let search_path = bucket_path. join ( & search_dir) ;
95
+ debug ! ( "Search path: {:?}" , search_path) ;
96
+
97
+ if !search_path. is_dir ( ) {
98
+ return Ok ( ( Vec :: new ( ) , Vec :: new ( ) ) ) ;
99
+ }
100
+
101
+ let ( mut objects, common_prefixes) = self
102
+ . list_contents (
103
+ delimiter. is_some ( ) && prefix. as_deref ( ) . map_or ( false , |p| !p. is_empty ( ) ) ,
104
+ & bucket_path,
105
+ & search_path,
106
+ & prefix_filter,
107
+ delimiter,
108
+ max_keys,
109
+ )
110
+ . await ?;
111
+
112
+ objects. sort_by ( |a, b| a. key . cmp ( & b. key ) ) ;
113
+ let mut common_prefixes: Vec < _ > = common_prefixes. into_iter ( ) . collect ( ) ;
114
+ common_prefixes. sort ( ) ;
115
+
116
+ Ok ( ( objects, common_prefixes) )
117
+ }
118
+
50
119
/// Constructs a file system storage located at `root`
51
120
/// # Errors
52
121
/// Returns an `Err` if current working directory is invalid or `root` doesn't exist
@@ -57,12 +126,42 @@ impl FileSystem {
57
126
58
127
/// resolve object path under the virtual root
59
128
fn get_object_path ( & self , bucket : & str , key : & str ) -> io:: Result < PathBuf > {
60
- let dir = Path :: new ( & bucket) ;
61
- let file_path = Path :: new ( & key) ;
62
- let ans = dir. join ( file_path) . absolutize_virtually ( & self . root ) ?. into ( ) ;
129
+ let bucket_path = self . get_bucket_path ( bucket) ?;
130
+ let file_path = Path :: new ( key) ;
131
+ let ans = bucket_path
132
+ . join ( file_path)
133
+ . absolutize_virtually ( & self . root ) ?
134
+ . into ( ) ;
63
135
Ok ( ans)
64
136
}
65
137
138
+ /// Wrap get_object_path with URL decoding and filename sanitization
139
+ fn get_unsanitized_object_path ( & self , bucket : & str , key : & str ) -> io:: Result < PathBuf > {
140
+ let decoded_key =
141
+ urlencoding:: decode ( key) . map_err ( |e| io:: Error :: new ( io:: ErrorKind :: InvalidInput , e) ) ?;
142
+
143
+ // Sanitize filename
144
+ let sanitized_key = decoded_key
145
+ . chars ( )
146
+ . map ( |c| {
147
+ if c. is_ascii_alphanumeric ( ) || c == '.' || c == '-' || c == '_' || c == '/' {
148
+ c
149
+ } else {
150
+ '_'
151
+ }
152
+ } )
153
+ . collect :: < String > ( ) ;
154
+
155
+ // if sanitized_key.split('/').any(|component| component == "..") {
156
+ // return Err(io::Error::new(
157
+ // io::ErrorKind::InvalidInput,
158
+ // "Invalid path: contains '..' sequence",
159
+ // ));
160
+ // }
161
+
162
+ self . get_object_path ( bucket, & sanitized_key)
163
+ }
164
+
66
165
/// resolve bucket path under the virtual root
67
166
fn get_bucket_path ( & self , bucket : & str ) -> io:: Result < PathBuf > {
68
167
let dir = Path :: new ( & bucket) ;
@@ -377,8 +476,20 @@ impl S3Storage for FileSystem {
377
476
AmzCopySource :: Bucket { bucket, key } => ( bucket, key) ,
378
477
} ;
379
478
380
- let src_path = trace_try ! ( self . get_object_path( bucket, key) ) ;
479
+ let src_path = trace_try ! ( self . get_unsanitized_object_path( bucket, key) ) ;
480
+ debug ! (
481
+ "CopyObject: src_path = {}, bucket = {}, key = {}" ,
482
+ src_path. display( ) ,
483
+ bucket,
484
+ key,
485
+ ) ;
381
486
let dst_path = trace_try ! ( self . get_object_path( & input. bucket, & input. key) ) ;
487
+ debug ! (
488
+ "CopyObject: dst_path = {}, input.bucket = {}, input.key = {}" ,
489
+ dst_path. display( ) ,
490
+ input. bucket,
491
+ input. key
492
+ ) ;
382
493
383
494
let file_metadata = trace_try ! ( async_fs:: metadata( & src_path) . await ) ;
384
495
let last_modified = time:: to_rfc3339 ( trace_try ! ( file_metadata. modified( ) ) ) ;
@@ -661,57 +772,21 @@ impl S3Storage for FileSystem {
661
772
& self ,
662
773
input : ListObjectsRequest ,
663
774
) -> S3StorageResult < ListObjectsOutput , ListObjectsError > {
664
- let bucket_path = trace_try ! ( self . get_bucket_path( & input. bucket) ) ;
665
- debug ! ( "Bucket path: {:?}" , bucket_path) ;
666
-
667
- let ( search_dir, prefix_filter) =
668
- if input. delimiter . is_none ( ) || input. prefix . as_deref ( ) . unwrap_or ( "" ) . is_empty ( ) {
669
- ( PathBuf :: new ( ) , String :: new ( ) )
670
- } else {
671
- self . parse_prefix_and_delimiter ( & input. prefix , & input. delimiter )
672
- } ;
673
- let search_path = bucket_path. join ( & search_dir) ;
674
- debug ! ( "Search path: {:?}" , search_path) ;
675
-
676
- if !search_path. is_dir ( ) {
677
- return Ok ( ListObjectsOutput {
678
- name : Some ( input. bucket ) ,
679
- prefix : input. prefix ,
680
- delimiter : input. delimiter ,
681
- encoding_type : input. encoding_type ,
682
- ..Default :: default ( )
683
- } ) ;
684
- }
685
-
686
- let ( mut objects, common_prefixes) = trace_try ! (
687
- self . list_contents(
688
- input. delimiter. is_some( )
689
- && input. prefix. as_deref( ) . map_or( false , |p| !p. is_empty( ) ) ,
690
- & bucket_path,
691
- & search_path,
692
- & prefix_filter,
775
+ let ( objects, common_prefixes) = trace_try ! (
776
+ self . abstract_list_objects(
777
+ & input. bucket,
778
+ & input. prefix,
693
779
& input. delimiter,
694
780
input. max_keys. unwrap_or( 1000i64 ) ,
695
781
)
696
782
. await
697
783
) ;
698
784
699
- objects. sort_by ( |a, b| a. key . cmp ( & b. key ) ) ;
700
- let mut common_prefixes: Vec < _ > = common_prefixes. into_iter ( ) . collect ( ) ;
701
- common_prefixes. sort ( ) ;
702
-
703
- // URL encode object keys and common prefixes if encoding type is specified
704
- if input. encoding_type . as_deref ( ) == Some ( "url" ) {
705
- for object in objects. iter_mut ( ) {
706
- if let Some ( key) = object. key . as_mut ( ) {
707
- * key = urlencoding:: encode ( key) . into_owned ( ) ;
708
- }
709
- }
710
- common_prefixes = common_prefixes
711
- . into_iter ( )
712
- . map ( |p| urlencoding:: encode ( & p) . into_owned ( ) )
713
- . collect ( ) ;
714
- }
785
+ let ( objects, common_prefixes) = if input. encoding_type . as_deref ( ) == Some ( "url" ) {
786
+ Self :: url_encode_list_results ( objects, common_prefixes)
787
+ } else {
788
+ ( objects, common_prefixes)
789
+ } ;
715
790
716
791
Ok ( ListObjectsOutput {
717
792
contents : Some ( objects) ,
@@ -741,61 +816,24 @@ impl S3Storage for FileSystem {
741
816
& self ,
742
817
input : ListObjectsV2Request ,
743
818
) -> S3StorageResult < ListObjectsV2Output , ListObjectsV2Error > {
744
- let bucket_path = trace_try ! ( self . get_bucket_path( & input. bucket) ) ;
745
- debug ! ( "Bucket path: {:?}" , bucket_path) ;
746
-
747
- let ( search_dir, prefix_filter) =
748
- if input. delimiter . is_none ( ) || input. prefix . as_deref ( ) . unwrap_or ( "" ) . is_empty ( ) {
749
- ( PathBuf :: new ( ) , String :: new ( ) )
750
- } else {
751
- self . parse_prefix_and_delimiter ( & input. prefix , & input. delimiter )
752
- } ;
753
- let search_path = bucket_path. join ( & search_dir) ;
754
- debug ! ( "Search path: {:?}" , search_path) ;
755
-
756
- if !search_path. is_dir ( ) {
757
- return Ok ( ListObjectsV2Output {
758
- name : Some ( input. bucket ) ,
759
- prefix : input. prefix ,
760
- delimiter : input. delimiter ,
761
- key_count : Some ( 0 ) ,
762
- encoding_type : input. encoding_type . clone ( ) ,
763
- ..Default :: default ( )
764
- } ) ;
765
- }
766
-
767
- let ( mut objects, common_prefixes) = trace_try ! (
768
- self . list_contents(
769
- input. delimiter. is_some( )
770
- && input. prefix. as_deref( ) . map_or( false , |p| !p. is_empty( ) ) ,
771
- & bucket_path,
772
- & search_path,
773
- & prefix_filter,
819
+ let ( objects, common_prefixes) = trace_try ! (
820
+ self . abstract_list_objects(
821
+ & input. bucket,
822
+ & input. prefix,
774
823
& input. delimiter,
775
824
input. max_keys. unwrap_or( 1000i64 ) ,
776
825
)
777
826
. await
778
827
) ;
779
828
780
- objects. sort_by ( |a, b| a. key . cmp ( & b. key ) ) ;
781
- let mut common_prefixes: Vec < _ > = common_prefixes. into_iter ( ) . collect ( ) ;
782
- common_prefixes. sort ( ) ;
783
-
784
829
let object_count = objects. len ( ) ;
785
830
let common_prefix_count = common_prefixes. len ( ) ;
786
831
787
- // URL encode object keys if encoding type is specified
788
- if input. encoding_type . as_deref ( ) == Some ( "url" ) {
789
- for object in objects. iter_mut ( ) {
790
- if let Some ( key) = object. key . as_mut ( ) {
791
- * key = urlencoding:: encode ( key) . into_owned ( ) ;
792
- }
793
- }
794
- common_prefixes = common_prefixes
795
- . into_iter ( )
796
- . map ( |p| urlencoding:: encode ( & p) . into_owned ( ) )
797
- . collect ( ) ;
798
- }
832
+ let ( objects, common_prefixes) = if input. encoding_type . as_deref ( ) == Some ( "url" ) {
833
+ Self :: url_encode_list_results ( objects, common_prefixes)
834
+ } else {
835
+ ( objects, common_prefixes)
836
+ } ;
799
837
800
838
Ok ( ListObjectsV2Output {
801
839
contents : Some ( objects) ,
0 commit comments