@@ -51,11 +51,12 @@ use crate::multipart::{MultipartConfig, MultipartParser};
5151use crate :: response:: { get_mime_type, HttpResponse } ;
5252use crate :: templates:: TemplateEngine ;
5353use glob:: Pattern ;
54- use log:: { debug, error, info, warn} ;
54+ use log:: { error, info, warn} ;
55+ use rand;
5556use std:: collections:: HashMap ;
5657use std:: env;
5758use std:: fs:: { self , File , OpenOptions } ;
58- use std:: io:: { Cursor , Write } ;
59+ use std:: io:: { Cursor , Read , Write } ;
5960use std:: path:: { Path , PathBuf } ;
6061
6162/// Temporary file prefix for atomic operations
@@ -406,13 +407,79 @@ impl UploadHandler {
406407 // Validate filename
407408 self . validate_filename ( & original_filename) ?;
408409
409- // Read file content
410- let file_content = part. read_to_bytes ( ) ?;
411- total_bytes += file_content. len ( ) as u64 ;
410+ // Get file size from Content-Length header if available
411+ let content_length = part
412+ . headers
413+ . headers
414+ . get ( "content-length" )
415+ . and_then ( |len| len. parse :: < u64 > ( ) . ok ( ) ) ;
416+
417+ // Check file size limit early if we know the size
418+ if let Some ( length) = content_length {
419+ if length > self . max_file_size {
420+ return Err ( AppError :: payload_too_large ( self . max_file_size ) ) ;
421+ }
422+ total_bytes += length;
423+ }
424+
425+ // Create a temporary file for streaming the upload
426+ let temp_filename = format ! (
427+ "{}{}_{}_{:x}.tmp" ,
428+ TEMP_FILE_PREFIX ,
429+ std:: process:: id( ) ,
430+ std:: time:: SystemTime :: now( )
431+ . duration_since( std:: time:: UNIX_EPOCH )
432+ . unwrap_or_default( )
433+ . as_nanos( ) ,
434+ rand:: random:: <u32 >( ) // Add randomness for uniqueness
435+ ) ;
436+ let temp_path = self . target_dir . join ( & temp_filename) ;
412437
413- // Additional size check per file
414- if file_content. len ( ) as u64 > self . max_file_size {
415- return Err ( AppError :: payload_too_large ( self . max_file_size ) ) ;
438+ // Stream the file content directly to disk instead of loading it into memory
439+ let mut temp_file = File :: create ( & temp_path) . map_err ( |e| {
440+ error ! ( "Failed to create temporary file {temp_path:?}: {e}" ) ;
441+ AppError :: from ( e)
442+ } ) ?;
443+
444+ // Use a reasonably sized buffer for streaming
445+ let mut buffer = [ 0u8 ; 64 * 1024 ] ; // 64KB buffer
446+ let mut file_size: u64 = 0 ;
447+
448+ // Stream data from part reader to file
449+ loop {
450+ match part. reader . read ( & mut buffer) {
451+ Ok ( 0 ) => break , // End of file
452+ Ok ( bytes_read) => {
453+ temp_file. write_all ( & buffer[ ..bytes_read] ) . map_err ( |e| {
454+ error ! ( "Failed to write to temporary file {temp_path:?}: {e}" ) ;
455+ let _ = fs:: remove_file ( & temp_path) ; // Cleanup on error
456+ AppError :: from ( e)
457+ } ) ?;
458+ file_size += bytes_read as u64 ;
459+
460+ // Check size limit during streaming
461+ if file_size > self . max_file_size {
462+ let _ = fs:: remove_file ( & temp_path) ; // Cleanup
463+ return Err ( AppError :: payload_too_large ( self . max_file_size ) ) ;
464+ }
465+ }
466+ Err ( e) => {
467+ let _ = fs:: remove_file ( & temp_path) ; // Cleanup on error
468+ return Err ( AppError :: Io ( e) ) ;
469+ }
470+ }
471+ }
472+
473+ // Sync file to ensure data is written
474+ temp_file. sync_all ( ) . map_err ( |e| {
475+ error ! ( "Failed to sync temporary file {temp_path:?}: {e}" ) ;
476+ let _ = fs:: remove_file ( & temp_path) ; // Cleanup on error
477+ AppError :: from ( e)
478+ } ) ?;
479+
480+ // Update total bytes if we didn't have Content-Length
481+ if content_length. is_none ( ) {
482+ total_bytes += file_size;
416483 }
417484
418485 // Validate file extension
@@ -426,8 +493,14 @@ impl UploadHandler {
426493 self . generate_unique_filename ( & original_filename) ?;
427494 let target_path = self . target_dir . join ( & final_filename) ;
428495
429- // Write file atomically
430- let saved_path = self . write_file_atomically ( & target_path, & file_content) ?;
496+ // Rename the temporary file to the target path (atomic operation)
497+ let saved_path = fs:: rename ( & temp_path, & target_path)
498+ . map ( |_| target_path. clone ( ) )
499+ . map_err ( |e| {
500+ error ! ( "Failed to rename {temp_path:?} to {target_path:?}: {e}" ) ;
501+ let _ = fs:: remove_file ( & temp_path) ; // Cleanup on error
502+ AppError :: from ( e)
503+ } ) ?;
431504
432505 if was_renamed {
433506 warnings. push ( format ! (
@@ -439,15 +512,15 @@ impl UploadHandler {
439512 original_name : original_filename,
440513 saved_name : final_filename,
441514 saved_path,
442- size : file_content . len ( ) as u64 ,
515+ size : file_size ,
443516 mime_type,
444517 renamed : was_renamed,
445518 } ) ;
446519
447520 info ! (
448521 "Successfully uploaded file: {} ({} bytes)" ,
449522 uploaded_files. last( ) . unwrap( ) . saved_name,
450- file_content . len ( )
523+ file_size
451524 ) ;
452525 }
453526
@@ -596,56 +669,9 @@ impl UploadHandler {
596669 ) )
597670 }
598671
599- /// Write file atomically using temporary file and rename
600- fn write_file_atomically (
601- & self ,
602- target_path : & Path ,
603- content : & [ u8 ] ,
604- ) -> Result < PathBuf , AppError > {
605- // Create temporary file in same directory with unique name
606- let nanos = std:: time:: SystemTime :: now ( )
607- . duration_since ( std:: time:: UNIX_EPOCH )
608- . unwrap_or_default ( )
609- . as_nanos ( ) ;
610- let temp_filename = format ! (
611- "{}{}_{}_{:x}.tmp" ,
612- TEMP_FILE_PREFIX ,
613- std:: process:: id( ) ,
614- nanos,
615- nanos. wrapping_mul( 7919 ) // Simple hash to add more uniqueness
616- ) ;
617- let temp_path = self . target_dir . join ( temp_filename) ;
618-
619- // Write to temporary file
620- {
621- let mut temp_file = File :: create ( & temp_path) . map_err ( |e| {
622- error ! ( "Failed to create temporary file {temp_path:?}: {e}" ) ;
623- AppError :: from ( e)
624- } ) ?;
625-
626- temp_file. write_all ( content) . map_err ( |e| {
627- error ! ( "Failed to write to temporary file {temp_path:?}: {e}" ) ;
628- let _ = fs:: remove_file ( & temp_path) ; // Cleanup on error
629- AppError :: from ( e)
630- } ) ?;
631-
632- temp_file. sync_all ( ) . map_err ( |e| {
633- error ! ( "Failed to sync temporary file {temp_path:?}: {e}" ) ;
634- let _ = fs:: remove_file ( & temp_path) ; // Cleanup on error
635- AppError :: from ( e)
636- } ) ?;
637- }
638-
639- // Atomically rename temporary file to target
640- fs:: rename ( & temp_path, target_path) . map_err ( |e| {
641- error ! ( "Failed to rename {temp_path:?} to {target_path:?}: {e}" ) ;
642- let _ = fs:: remove_file ( & temp_path) ; // Cleanup on error
643- AppError :: from ( e)
644- } ) ?;
645-
646- debug ! ( "Successfully wrote file atomically to {target_path:?}" ) ;
647- Ok ( target_path. to_path_buf ( ) )
648- }
672+ // The write_file_atomically method has been removed as it's no longer used.
673+ // File uploads now use direct streaming to disk with atomic rename operations
674+ // to avoid loading entire files into memory.
649675
650676 /// Generate appropriate response based on request Accept header
651677 fn generate_upload_response (
0 commit comments