@@ -14,11 +14,49 @@ const { config } = require('../../../Config');
1414const validateWebsiteHeader = require ( './websiteServing' )
1515 . validateWebsiteHeader ;
1616const applyZenkoUserMD = require ( './applyZenkoUserMD' ) ;
17+ const {
18+ algorithms,
19+ getChecksumDataFromHeaders,
20+ arsenalErrorFromChecksumError,
21+ } = require ( '../integrity/validateChecksums' ) ;
1722
1823const { externalBackends, versioningNotImplBackends } = constants ;
1924
2025const externalVersioningErrorMessage = 'We do not currently support putting ' +
21- 'a versioned object to a location-constraint of type Azure or GCP.' ;
26+ 'a versioned object to a location-constraint of type Azure or GCP.' ;
27+
28+ /**
29+ * Validate and compute the checksum for a zero-size object body.
30+ * Parses the checksum headers, validates the client-supplied digest against
31+ * the empty-body hash, sets metadataStoreParams.checksum on success, and
32+ * calls back with an error on mismatch or invalid headers.
33+ *
34+ * @param {object } headers - request headers
35+ * @param {object } metadataStoreParams - metadata params (checksum field set in-place)
36+ * @param {function } callback - (err) callback
37+ * @return {undefined }
38+ */
39+ function zeroSizeBodyChecksumCheck ( headers , metadataStoreParams , callback ) {
40+ const checksumData = getChecksumDataFromHeaders ( headers ) ;
41+ if ( checksumData . error ) {
42+ return callback ( arsenalErrorFromChecksumError ( checksumData ) ) ;
43+ }
44+ // For trailer format with zero decoded bytes, the trailer in the body is
45+ // never read (stream bypassed), so expected is always undefined here.
46+ // We still compute and store the empty-body hash for the announced algorithm.
47+ const { algorithm, expected } = checksumData ;
48+ return Promise . resolve ( algorithms [ algorithm ] . digest ( Buffer . alloc ( 0 ) ) )
49+ . then ( value => {
50+ if ( expected !== undefined && expected !== value ) {
51+ return callback ( errorInstances . BadDigest . customizeDescription (
52+ `The ${ algorithm . toUpperCase ( ) } you specified did not match the calculated checksum.`
53+ ) ) ;
54+ }
55+ // eslint-disable-next-line no-param-reassign
56+ metadataStoreParams . checksum = { algorithm, value, type : 'FULL_OBJECT' } ;
57+ return callback ( null ) ;
58+ } , err => callback ( err ) ) ;
59+ }
2260
2361function _storeInMDandDeleteData ( bucketName , dataGetInfo , cipherBundle ,
2462 metadataStoreParams , dataToDelete , log , requestMethod , callback ) {
@@ -217,7 +255,13 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo,
217255 if ( size === 0 ) {
218256 if ( ! dontSkipBackend [ locationType ] ) {
219257 metadataStoreParams . contentMD5 = constants . emptyFileMd5 ;
220- return next ( null , null , null , null ) ;
258+ // Delete markers are zero-byte versioned tombstones with
259+ // no body, ETag, or checksum — skip checksum handling.
260+ if ( isDeleteMarker ) {
261+ return next ( null , null , null , null ) ;
262+ }
263+ return zeroSizeBodyChecksumCheck ( request . headers , metadataStoreParams ,
264+ err => next ( err , null , null , null ) ) ;
221265 }
222266
223267 // Handle mdOnlyHeader as a metadata only operation. If
@@ -264,7 +308,8 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo,
264308 : `1:${ calculatedHash } ` ;
265309 const dataGetInfoArr = [ { key, size, start : 0 , dataStoreName,
266310 dataStoreType, dataStoreETag : prefixedDataStoreETag ,
267- dataStoreVersionId } ] ;
311+ dataStoreVersionId
312+ } ] ;
268313 if ( cipherBundle ) {
269314 dataGetInfoArr [ 0 ] . cryptoScheme = cipherBundle . cryptoScheme ;
270315 dataGetInfoArr [ 0 ] . cipheredDataKey =
0 commit comments