@@ -27,6 +27,8 @@ use open;
2727use path_dedot:: ParseDot ;
2828use percent_encoding:: percent_decode;
2929use pretty_bytes:: converter:: convert;
30+ use rand:: distributions:: Alphanumeric ;
31+ use rand:: { thread_rng, Rng } ;
3032use termcolor:: { Color , ColorSpec } ;
3133
3234use color:: { build_spec, Printer } ;
@@ -69,7 +71,7 @@ fn main() {
6971 . arg ( clap:: Arg :: with_name ( "upload" )
7072 . short ( "u" )
7173 . long ( "upload" )
72- . help ( "Enable upload files (multiple select)" ) )
74+ . help ( "Enable upload files. (multiple select) (CSRF token required )" ) )
7375 . arg ( clap:: Arg :: with_name ( "redirect" ) . long ( "redirect" )
7476 . takes_value ( true )
7577 . validator ( |url_string| iron:: Url :: parse ( url_string. as_str ( ) ) . map ( |_| ( ) ) )
@@ -209,7 +211,7 @@ fn main() {
209211 . map ( |s| PathBuf :: from ( s) . canonicalize ( ) . unwrap ( ) )
210212 . unwrap_or_else ( || env:: current_dir ( ) . unwrap ( ) ) ;
211213 let index = matches. is_present ( "index" ) ;
212- let upload = matches. is_present ( "upload" ) ;
214+ let upload_arg = matches. is_present ( "upload" ) ;
213215 let redirect_to = matches
214216 . value_of ( "redirect" )
215217 . map ( iron:: Url :: parse)
@@ -261,10 +263,22 @@ fn main() {
261263
262264 let silent = matches. is_present ( "silent" ) ;
263265
266+ let upload: Option < Upload > = if upload_arg {
267+ let token: String = thread_rng ( )
268+ . sample_iter ( & Alphanumeric )
269+ . take ( 10 )
270+ . map ( char:: from)
271+ . collect ( ) ;
272+ Some ( Upload { csrf_token : token } )
273+ } else {
274+ None
275+ } ;
276+
264277 if !silent {
265278 printer
266279 . println_out (
267- r#" Index: {}, Upload: {}, Cache: {}, Cors: {}, Range: {}, Sort: {}, Threads: {}
280+ r#" Index: {}, Cache: {}, Cors: {}, Range: {}, Sort: {}, Threads: {}
281+ Upload: {}, CSRF Token: {}
268282 Auth: {}, Compression: {}
269283 https: {}, Cert: {}, Cert-Password: {}
270284 Root: {},
@@ -273,12 +287,18 @@ fn main() {
273287 ======== [{}] ========"# ,
274288 & vec ! [
275289 enable_string( index) ,
276- enable_string( upload) ,
277290 enable_string( cache) ,
278291 enable_string( cors) ,
279292 enable_string( range) ,
280293 enable_string( sort) ,
281294 threads. to_string( ) ,
295+ enable_string( upload_arg) ,
296+ ( if upload. is_some( ) {
297+ upload. as_ref( ) . unwrap( ) . csrf_token. as_str( )
298+ } else {
299+ ""
300+ } )
301+ . to_string( ) ,
282302 auth. unwrap_or( "disabled" ) . to_string( ) ,
283303 compression_string,
284304 ( if cert. is_some( ) {
@@ -381,11 +401,14 @@ fn main() {
381401 std:: process:: exit ( 1 ) ;
382402 } ;
383403}
404+ struct Upload {
405+ csrf_token : String ,
406+ }
384407
385408struct MainHandler {
386409 root : PathBuf ,
387410 index : bool ,
388- upload : bool ,
411+ upload : Option < Upload > ,
389412 cache : bool ,
390413 range : bool ,
391414 redirect_to : Option < iron:: Url > ,
@@ -433,7 +456,7 @@ impl Handler for MainHandler {
433456 ) ) ;
434457 }
435458
436- if self . upload && req. method == method:: Post {
459+ if self . upload . is_some ( ) && req. method == method:: Post {
437460 if let Err ( ( s, msg) ) = self . save_files ( req, & fs_path) {
438461 return Ok ( error_resp ( s, & msg) ) ;
439462 } else {
@@ -485,26 +508,62 @@ impl MainHandler {
485508 // in a new temporary directory under the OS temporary directory.
486509 match multipart. save ( ) . size_limit ( self . upload_size_limit ) . temp ( ) {
487510 SaveResult :: Full ( entries) => {
488- for ( _, fields) in entries. fields {
489- for field in fields {
490- let mut data = field. data . readable ( ) . unwrap ( ) ;
491- let headers = & field. headers ;
492- let mut target_path = path. clone ( ) ;
493-
494- target_path. push ( headers. filename . clone ( ) . unwrap ( ) ) ;
495- if let Err ( errno) = std:: fs:: File :: create ( target_path)
496- . and_then ( |mut file| io:: copy ( & mut data, & mut file) )
497- {
498- return Err ( (
499- status:: InternalServerError ,
500- format ! ( "Copy file failed: {}" , errno) ,
501- ) ) ;
502- } else {
503- println ! (
504- " >> File saved: {}" ,
505- headers. filename. clone( ) . unwrap( )
506- ) ;
507- }
511+ // Pull out csrf field to check if token matches one generated
512+ let csrf_field = match entries
513+ . fields
514+ . get ( "csrf" )
515+ . map ( |fields| fields. first ( ) )
516+ . unwrap_or ( None )
517+ {
518+ Some ( field) => field,
519+ None => {
520+ return Err ( (
521+ status:: BadRequest ,
522+ String :: from ( "csrf parameter not provided" ) ,
523+ ) )
524+ }
525+ } ;
526+
527+ // Read token value from field
528+ let mut token = String :: new ( ) ;
529+ csrf_field
530+ . data
531+ . readable ( )
532+ . unwrap ( )
533+ . read_to_string ( & mut token)
534+ . unwrap ( ) ;
535+
536+ // Check if they match
537+ if self . upload . as_ref ( ) . unwrap ( ) . csrf_token != token {
538+ return Err ( (
539+ status:: BadRequest ,
540+ String :: from ( "csrf token does not match" ) ,
541+ ) ) ;
542+ }
543+
544+ // Grab all the fields named files
545+ let files_fields = match entries. fields . get ( "files" ) {
546+ Some ( fields) => fields,
547+ None => {
548+ return Err ( ( status:: BadRequest , String :: from ( "no files provided" ) ) )
549+ }
550+ } ;
551+
552+ for field in files_fields {
553+ let mut data = field. data . readable ( ) . unwrap ( ) ;
554+ let headers = & field. headers ;
555+ let mut target_path = path. clone ( ) ;
556+
557+ target_path. push ( headers. filename . clone ( ) . unwrap ( ) ) ;
558+ if let Err ( errno) = std:: fs:: File :: create ( target_path)
559+ . and_then ( |mut file| io:: copy ( & mut data, & mut file) )
560+ {
561+ return Err ( (
562+ status:: InternalServerError ,
563+ format ! ( "Copy file failed: {}" , errno) ,
564+ ) ) ;
565+ } else {
566+ println ! ( " >> File saved: {}" , headers. filename. clone( ) . unwrap( ) ) ;
508567 }
509568 }
510569 Ok ( ( ) )
@@ -738,16 +797,18 @@ impl MainHandler {
738797 ) ) ;
739798 }
740799
741- // Optinal upload form
742- let upload_form = if self . upload {
800+ // Optional upload form
801+ let upload_form = if self . upload . is_some ( ) {
743802 format ! (
744803 r#"
745804<form style="margin-top:1em; margin-bottom:1em;" action="/{path}" method="POST" enctype="multipart/form-data">
746805 <input type="file" name="files" accept="*" multiple />
806+ <input type="hidden" name="csrf" value="{csrf}"/>
747807 <input type="submit" value="Upload" />
748808</form>
749809"# ,
750- path = encode_link_path( path_prefix)
810+ path = encode_link_path( path_prefix) ,
811+ csrf = self . upload. as_ref( ) . unwrap( ) . csrf_token
751812 )
752813 } else {
753814 "" . to_owned ( )
0 commit comments