@@ -211,29 +211,29 @@ export class S3TransferManager implements IS3TransferManager {
211211 /**
212212 * What is missing from the revised SEP and this implementation currently?
213213 * PART mode:
214- * - Step 5: validate GetObject response for each part
215- * - If validation fails at any point, cancel all ongoing requests and error out
214+ * - (DONE) Step 5: validate GetObject response for each part
215+ * - If validation fails at any point, cancel all ongoing requests and error out
216216 * - Step 6: after all requests have been sent, validate that the total number of part GET requests sent matches with the
217217 * expected `PartsCount`
218218 * - Step 7: when creating DownloadResponse, set accordingly:
219- * - (DONE) `ContentLength` : total length of the object saved from Step 3
220- * - (DONE) `ContentRange`: based on `bytes 0-(ContentLength -1)/ContentLength`
221- * - If ChecksumType is `COMPOSITE`, set all checksum value members to null as
222- * the checksum value returned from a part GET request is not the composite
223- * checksum for the entire object
219+ * - (DONE) `ContentLength` : total length of the object saved from Step 3
220+ * - (DONE) `ContentRange`: based on `bytes 0-(ContentLength -1)/ContentLength`
221+ * - If ChecksumType is `COMPOSITE`, set all checksum value members to null as
222+ * the checksum value returned from a part GET request is not the composite
223+ * checksum for the entire object
224224 * RANGE mode:
225- * - Step 7: validate GetObject response for each part. If validation fails or a
225+ * - (DONE) Step 7: validate GetObject response for each part. If validation fails or a
226226 * request fails at any point, cancel all ongoing requests and return an error to
227227 * the user.
228228 * - Step 8: after all requests have sent, validate that the total number of ranged
229229 * GET requests sent matches with the expected number saved from Step 5.
230230 * - Step 9: create DownloadResponse. Copy the fields in GetObject response from
231231 * Step 3 and set the following fields accordingly:
232- * - (DONE) `ContentLength` : total length of the object saved from Step 3
233- * - (DONE) `ContentRange`: based on `bytes 0-(ContentLength -1)/ContentLength`
234- * - If ChecksumType is `COMPOSITE`, set all checksum value members to null as
235- * the checksum value returned from a part GET request is not the composite
236- * checksum for the entire object
232+ * - (DONE) `ContentLength` : total length of the object saved from Step 3
233+ * - (DONE) `ContentRange`: based on `bytes 0-(ContentLength -1)/ContentLength`
234+ * - If ChecksumType is `COMPOSITE`, set all checksum value members to null as
235+ * the checksum value returned from a part GET request is not the composite
236+ * checksum for the entire object
237237 * Checksum validation notes:
238238 * -
239239 *
@@ -370,8 +370,8 @@ export class S3TransferManager implements IS3TransferManager {
370370 } ;
371371 const initialPart = await this . s3ClientInstance . send ( new GetObjectCommand ( initialPartRequest ) , transferOptions ) ;
372372 const initialETag = initialPart . ETag ?? undefined ;
373+ const partSize = initialPart . ContentLength ;
373374 totalSize = initialPart . ContentRange ? Number . parseInt ( initialPart . ContentRange . split ( "/" ) [ 1 ] ) : undefined ;
374-
375375 this . dispatchTransferInitiatedEvent ( request , totalSize ) ;
376376 if ( initialPart . Body ) {
377377 if ( initialPart . Body && typeof ( initialPart . Body as any ) . getReader === "function" ) {
@@ -383,9 +383,12 @@ export class S3TransferManager implements IS3TransferManager {
383383 streams . push ( Promise . resolve ( initialPart . Body ) ) ;
384384 requests . push ( initialPartRequest ) ;
385385 }
386+
386387 this . updateResponseLengthAndRange ( initialPart , totalSize ) ;
387388 this . assignMetadata ( metadata , initialPart ) ;
389+ this . updateChecksumValues ( initialPart , metadata ) ;
388390
391+ let partCount = 1 ;
389392 if ( initialPart . PartsCount ! > 1 ) {
390393 for ( let part = 2 ; part <= initialPart . PartsCount ! ; part ++ ) {
391394 this . checkAborted ( transferOptions ) ;
@@ -398,7 +401,7 @@ export class S3TransferManager implements IS3TransferManager {
398401 const getObject = this . s3ClientInstance
399402 . send ( new GetObjectCommand ( getObjectRequest ) , transferOptions )
400403 . then ( ( response ) => {
401- // this.validatePartRange (part, response.ContentRange, this.targetPartSizeBytes );
404+ this . validatePartDownload ( part , response . ContentRange , partSize ?? 0 ) ;
402405 if ( response . Body && typeof ( response . Body as any ) . getReader === "function" ) {
403406 const reader = ( response . Body as any ) . getReader ( ) ;
404407 ( response . Body as any ) . getReader = function ( ) {
@@ -410,8 +413,15 @@ export class S3TransferManager implements IS3TransferManager {
410413
411414 streams . push ( getObject ) ;
412415 requests . push ( getObjectRequest ) ;
416+ partCount ++ ;
413417 }
414418 }
419+
420+ if ( partCount !== initialPart . PartsCount ) {
421+ throw new Error (
422+ `The number of parts downloaded (${ partCount } ) does not match the expected number (${ initialPart . PartsCount } )`
423+ ) ;
424+ }
415425 } else {
416426 this . checkAborted ( transferOptions ) ;
417427
@@ -428,6 +438,7 @@ export class S3TransferManager implements IS3TransferManager {
428438 }
429439 this . updateResponseLengthAndRange ( getObject , totalSize ) ;
430440 this . assignMetadata ( metadata , getObject ) ;
441+ this . updateChecksumValues ( getObject , metadata ) ;
431442 }
432443
433444 return {
@@ -447,6 +458,7 @@ export class S3TransferManager implements IS3TransferManager {
447458 let left = 0 ;
448459 let right = this . targetPartSizeBytes - 1 ;
449460 let maxRange = Number . POSITIVE_INFINITY ;
461+ let remainingLength = 1 ;
450462
451463 if ( request . Range != null ) {
452464 const [ userRangeLeft , userRangeRight ] = request . Range . replace ( "bytes=" , "" ) . split ( "-" ) . map ( Number ) ;
@@ -455,17 +467,25 @@ export class S3TransferManager implements IS3TransferManager {
455467 left = userRangeLeft ;
456468 right = Math . min ( userRangeRight , left + S3TransferManager . MIN_PART_SIZE - 1 ) ;
457469 }
458-
459- let remainingLength = 1 ;
460470 const getObjectRequest : GetObjectCommandInput = {
461471 ...request ,
462472 Range : `bytes=${ left } -${ right } ` ,
463473 } ;
464474 const initialRangeGet = await this . s3ClientInstance . send ( new GetObjectCommand ( getObjectRequest ) , transferOptions ) ;
475+ this . validateRangeDownload ( `bytes=${ left } -${ right } ` , initialRangeGet . ContentRange ) ;
465476 const initialETag = initialRangeGet . ETag ?? undefined ;
466477 const totalSize = initialRangeGet . ContentRange
467478 ? Number . parseInt ( initialRangeGet . ContentRange . split ( "/" ) [ 1 ] )
468479 : undefined ;
480+
481+ let expectedRequestCount = 1 ;
482+ if ( totalSize ) {
483+ const contentLength = totalSize ;
484+ const remainingBytes = Math . max ( 0 , contentLength - ( right - left + 1 ) ) ;
485+ const additionalRequests = Math . ceil ( remainingBytes / S3TransferManager . MIN_PART_SIZE ) ;
486+ expectedRequestCount += additionalRequests ;
487+ }
488+
469489 if ( initialRangeGet . Body && typeof ( initialRangeGet . Body as any ) . getReader === "function" ) {
470490 const reader = ( initialRangeGet . Body as any ) . getReader ( ) ;
471491 ( initialRangeGet . Body as any ) . getReader = function ( ) {
@@ -474,16 +494,17 @@ export class S3TransferManager implements IS3TransferManager {
474494 }
475495
476496 this . dispatchTransferInitiatedEvent ( request , totalSize ) ;
477-
478497 streams . push ( Promise . resolve ( initialRangeGet . Body ! ) ) ;
479498 requests . push ( getObjectRequest ) ;
499+
480500 this . updateResponseLengthAndRange ( initialRangeGet , totalSize ) ;
481501 this . assignMetadata ( metadata , initialRangeGet ) ;
502+ this . updateChecksumValues ( initialRangeGet , metadata ) ;
482503
483504 left = right + 1 ;
484505 right = Math . min ( left + S3TransferManager . MIN_PART_SIZE - 1 , maxRange ) ;
485506 remainingLength = totalSize ? Math . min ( right - left + 1 , Math . max ( 0 , totalSize - left ) ) : 0 ;
486-
507+ let actualRequestCount = 1 ;
487508 while ( remainingLength > 0 ) {
488509 this . checkAborted ( transferOptions ) ;
489510
@@ -496,6 +517,7 @@ export class S3TransferManager implements IS3TransferManager {
496517 const getObject = this . s3ClientInstance
497518 . send ( new GetObjectCommand ( getObjectRequest ) , transferOptions )
498519 . then ( ( response ) => {
520+ this . validateRangeDownload ( range , response . ContentRange ) ;
499521 if ( response . Body && typeof ( response . Body as any ) . getReader === "function" ) {
500522 const reader = ( response . Body as any ) . getReader ( ) ;
501523 ( response . Body as any ) . getReader = function ( ) {
@@ -507,12 +529,19 @@ export class S3TransferManager implements IS3TransferManager {
507529
508530 streams . push ( getObject ) ;
509531 requests . push ( getObjectRequest ) ;
532+ actualRequestCount ++ ;
510533
511534 left = right + 1 ;
512535 right = Math . min ( left + S3TransferManager . MIN_PART_SIZE - 1 , maxRange ) ;
513536 remainingLength = totalSize ? Math . min ( right - left + 1 , Math . max ( 0 , totalSize - left ) ) : 0 ;
514537 }
515538
539+ if ( expectedRequestCount !== actualRequestCount ) {
540+ throw new Error (
541+ `The number of ranged GET requests sent (${ actualRequestCount } ) does not match the expected number (${ expectedRequestCount } )`
542+ ) ;
543+ }
544+
516545 return {
517546 totalSize,
518547 } ;
@@ -541,6 +570,15 @@ export class S3TransferManager implements IS3TransferManager {
541570 }
542571 }
543572
573+ private updateChecksumValues ( initialPart : DownloadResponse , metadata : Omit < DownloadResponse , "Body" > ) {
574+ if ( initialPart . ChecksumType === "COMPOSITE" ) {
575+ metadata . ChecksumCRC32 = undefined ;
576+ metadata . ChecksumCRC32C = undefined ;
577+ metadata . ChecksumSHA1 = undefined ;
578+ metadata . ChecksumSHA256 = undefined ;
579+ }
580+ }
581+
544582 private checkAborted ( transferOptions ?: TransferOptions ) : void {
545583 if ( transferOptions ?. abortSignal ?. aborted ) {
546584 throw Object . assign ( new Error ( "Download aborted." ) , { name : "AbortError" } ) ;
@@ -592,7 +630,7 @@ export class S3TransferManager implements IS3TransferManager {
592630 }
593631 }
594632
595- private validatePartRange ( partNumber : number , contentRange : string | undefined , partSize : number ) {
633+ private validatePartDownload ( partNumber : number , contentRange : string | undefined , partSize : number ) {
596634 if ( ! contentRange ) return ;
597635
598636 const match = contentRange . match ( / b y t e s ( \d + ) - ( \d + ) \/ ( \d + ) / ) ;
@@ -605,15 +643,6 @@ export class S3TransferManager implements IS3TransferManager {
605643 const expectedStart = ( partNumber - 1 ) * partSize ;
606644 const expectedEnd = Math . min ( expectedStart + partSize - 1 , total - 1 ) ;
607645
608- // console.log({
609- // partNumber,
610- // start,
611- // end,
612- // total,
613- // expectedStart,
614- // expectedEnd
615- // });
616-
617646 if ( start !== expectedStart ) {
618647 throw new Error ( `Expected part ${ partNumber } to start at ${ expectedStart } but got ${ start } ` ) ;
619648 }
@@ -622,4 +651,31 @@ export class S3TransferManager implements IS3TransferManager {
622651 throw new Error ( `Expected part ${ partNumber } to end at ${ expectedEnd } but got ${ end } ` ) ;
623652 }
624653 }
654+
655+ private validateRangeDownload ( requestRange : string , responseRange : string | undefined ) {
656+ if ( ! responseRange ) return ;
657+
658+ const match = responseRange . match ( / b y t e s ( \d + ) - ( \d + ) \/ ( \d + ) / ) ;
659+ if ( ! match ) throw new Error ( `Invalid ContentRange format: ${ responseRange } ` ) ;
660+
661+ const start = Number . parseInt ( match [ 1 ] ) ;
662+ const end = Number . parseInt ( match [ 2 ] ) ;
663+ const total = Number . parseInt ( match [ 3 ] ) ;
664+
665+ const rangeMatch = requestRange . match ( / b y t e s = ( \d + ) - ( \d + ) / ) ;
666+ if ( ! rangeMatch ) throw new Error ( `Invalid Range format: ${ requestRange } ` ) ;
667+
668+ const expectedStart = Number . parseInt ( rangeMatch [ 1 ] ) ;
669+ const expectedEnd = Number . parseInt ( rangeMatch [ 2 ] ) ;
670+
671+ if ( start !== expectedStart ) {
672+ throw new Error ( `Expected range to start at ${ expectedStart } but got ${ start } ` ) ;
673+ }
674+
675+ const isFinalPart = end + 1 === total ;
676+
677+ if ( end !== expectedEnd && ! ( isFinalPart && end < expectedEnd ) ) {
678+ throw new Error ( `Expected range to end at ${ expectedEnd } but got ${ end } ` ) ;
679+ }
680+ }
625681}
0 commit comments