@@ -77,6 +77,14 @@ type Downloader struct {
7777	// operation requests made by the downloader. 
7878	ClientOptions  []func (* s3.Options )
7979
80+ 	// By default, the downloader verifies that individual part ranges align 
81+ 	// based on the configured part size. 
82+ 	// 
83+ 	// You can disable that with this flag, however, Amazon S3 recommends 
84+ 	// against doing so because it damages the durability posture of object 
85+ 	// downloads. 
86+ 	DisableValidateParts  bool 
87+ 
8088	// Defines the buffer strategy used when downloading a part. 
8189	// 
8290	// If a WriterReadFromProvider is given the Download manager 
@@ -404,6 +412,15 @@ func (d *downloader) tryDownloadChunk(params *s3.GetObjectInput, w io.Writer) (i
404412	if  err  !=  nil  {
405413		return  0 , err 
406414	}
415+ 
416+ 	if  ! d .cfg .DisableValidateParts  &&  params .Range  !=  nil  &&  resp .ContentRange  !=  nil  {
417+ 		expectStart , expectEnd  :=  parseContentRange (* params .Range )
418+ 		actualStart , actualEnd  :=  parseContentRange (* resp .ContentRange )
419+ 		if  isRangeMismatch (expectStart , expectEnd , actualStart , actualEnd ) {
420+ 			return  0 , fmt .Errorf ("invalid content range: expect %d-%d, got %d-%d" , expectStart , expectEnd , actualStart , actualEnd )
421+ 		}
422+ 	}
423+ 
407424	d .setTotalBytes (resp ) // Set total if not yet set. 
408425	d .once .Do (func () {
409426		d .etag  =  aws .ToString (resp .ETag )
@@ -422,6 +439,46 @@ func (d *downloader) tryDownloadChunk(params *s3.GetObjectInput, w io.Writer) (i
422439	return  n , nil 
423440}
424441
442+ func  parseContentRange (v  string ) (int , int ) {
443+ 	parts  :=  strings .Split (v , "/" ) // chop the total off, if it's there 
444+ 
445+ 	// we send "bytes=" but S3 appears to return "bytes ", handle both 
446+ 	trimmed  :=  strings .TrimPrefix (parts [0 ], "bytes " )
447+ 	trimmed  =  strings .TrimPrefix (trimmed , "bytes=" )
448+ 
449+ 	parts  =  strings .Split (trimmed , "-" )
450+ 	if  len (parts ) !=  2  {
451+ 		return  - 1 , - 1 
452+ 	}
453+ 
454+ 	start , err  :=  strconv .Atoi (parts [0 ])
455+ 	if  err  !=  nil  {
456+ 		return  - 1 , - 1 
457+ 	}
458+ 
459+ 	end , err  :=  strconv .Atoi (parts [1 ])
460+ 	if  err  !=  nil  {
461+ 		return  - 1 , - 1 
462+ 	}
463+ 
464+ 	return  start , end 
465+ }
466+ 
467+ func  isRangeMismatch (expectStart , expectEnd , actualStart , actualEnd  int ) bool  {
468+ 	if  expectStart  ==  - 1  ||  expectEnd  ==  - 1  ||  actualStart  ==  - 1  ||  actualEnd  ==  - 1  {
469+ 		return  false  // we don't know, one of the ranges was missing or unparseable 
470+ 	}
471+ 
472+ 	// for the final chunk (or the first chunk if it's smaller) we still 
473+ 	// request a full chunk but we get back the actual final part of the 
474+ 	// object, which will be smaller 
475+ 	if  expectStart  ==  actualStart  &&  actualEnd  <  expectEnd  {
476+ 		return  false 
477+ 	}
478+ 
479+ 	return  expectStart  !=  actualStart  ||  expectEnd  !=  actualEnd 
480+ }
481+ 
425482// getTotalBytes is a thread-safe getter for retrieving the total byte status. 
426483func  (d  * downloader ) getTotalBytes () int64  {
427484	d .m .Lock ()
0 commit comments