Skip to content

Commit 279f39b

Browse files
committed
CLDSRV-872: handle empty object checksum
1 parent be15a44 commit 279f39b

2 files changed

Lines changed: 52 additions & 7 deletions

File tree

lib/api/apiUtils/object/createAndStoreObject.js

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,49 @@ const { config } = require('../../../Config');
1414
const validateWebsiteHeader = require('./websiteServing')
1515
.validateWebsiteHeader;
1616
const applyZenkoUserMD = require('./applyZenkoUserMD');
17+
const {
18+
algorithms,
19+
getChecksumDataFromHeaders,
20+
arsenalErrorFromChecksumError,
21+
} = require('../integrity/validateChecksums');
1722

1823
const { externalBackends, versioningNotImplBackends } = constants;
1924

2025
const 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

2361
function _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 =

tests/functional/raw-node/test/checksumPutObjectUploadPart.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -759,11 +759,11 @@ describe('PutObject: checksum response header per algorithm', () => {
759759
});
760760

761761
const checksumAlgos = [
762-
{ name: 'crc32', computeExpected: () => algorithms.crc32.digest(body) },
763-
{ name: 'crc32c', computeExpected: () => algorithms.crc32c.digest(body) },
762+
{ name: 'crc32', computeExpected: () => algorithms.crc32.digest(body) },
763+
{ name: 'crc32c', computeExpected: () => algorithms.crc32c.digest(body) },
764764
{ name: 'crc64nvme', computeExpected: () => expectedCrc64nvme },
765-
{ name: 'sha1', computeExpected: () => algorithms.sha1.digest(body) },
766-
{ name: 'sha256', computeExpected: () => algorithms.sha256.digest(body) },
765+
{ name: 'sha1', computeExpected: () => algorithms.sha1.digest(body) },
766+
{ name: 'sha256', computeExpected: () => algorithms.sha256.digest(body) },
767767
];
768768

769769
for (const algo of checksumAlgos) {

0 commit comments

Comments
 (0)