diff --git a/lib/api/objectCopy.js b/lib/api/objectCopy.js
index 02b0a31d5b..e99a9e7c06 100644
--- a/lib/api/objectCopy.js
+++ b/lib/api/objectCopy.js
@@ -7,18 +7,15 @@ const validateHeaders = s3middleware.validateConditionalHeaders;
const constants = require('../../constants');
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
-const locationConstraintCheck
- = require('./apiUtils/object/locationConstraintCheck');
-const { checkQueryVersionId, versioningPreprocessing, decodeVID }
- = require('./apiUtils/object/versioning');
+const locationConstraintCheck = require('./apiUtils/object/locationConstraintCheck');
+const { checkQueryVersionId, versioningPreprocessing, decodeVID } = require('./apiUtils/object/versioning');
const getReplicationInfo = require('./apiUtils/object/getReplicationInfo');
const { data } = require('../data/wrapper');
const services = require('../services');
const { pushMetric } = require('../utapi/utilities');
const removeAWSChunked = require('./apiUtils/object/removeAWSChunked');
const { standardMetadataValidateBucketAndObj } = require('../metadata/metadataUtils');
-const validateWebsiteHeader = require('./apiUtils/object/websiteServing')
- .validateWebsiteHeader;
+const validateWebsiteHeader = require('./apiUtils/object/websiteServing').validateWebsiteHeader;
const { config } = require('../Config');
const monitoring = require('../utilities/monitoringHandler');
const applyZenkoUserMD = require('./apiUtils/object/applyZenkoUserMD');
@@ -32,8 +29,8 @@ const { initializeInternalLogRequestQueue, queueInternalLogRequest } = require('
const versionIdUtils = versioning.VersionID;
const locationHeader = constants.objectLocationConstraintHeader;
const versioningNotImplBackends = constants.versioningNotImplBackends;
-const externalVersioningErrorMessage = 'We do not currently support putting ' +
-'a versioned object to a location-constraint of type AWS or Azure or GCP.';
+const externalVersioningErrorMessage =
+ 'We do not currently support putting ' + 'a versioned object to a location-constraint of type AWS or Azure or GCP.';
/**
* Preps metadata to be saved (based on copy or replace request header)
@@ -53,8 +50,18 @@ const externalVersioningErrorMessage = 'We do not currently support putting ' +
* - sourceLocationConstraintName {string} - location type of the source
* - OR error
*/
-function _prepMetadata(request, sourceObjMD, headers, sourceIsDestination,
- authInfo, objectKey, sourceBucketMD, destBucketMD, sourceVersionId, log) {
+function _prepMetadata(
+ request,
+ sourceObjMD,
+ headers,
+ sourceIsDestination,
+ authInfo,
+ objectKey,
+ sourceBucketMD,
+ destBucketMD,
+ sourceVersionId,
+ log,
+) {
let whichMetadata = headers['x-amz-metadata-directive'];
// Default is COPY
whichMetadata = whichMetadata === undefined ? 'COPY' : whichMetadata;
@@ -65,45 +72,47 @@ function _prepMetadata(request, sourceObjMD, headers, sourceIsDestination,
// Default is COPY
whichTagging = whichTagging === undefined ? 'COPY' : whichTagging;
if (whichTagging !== 'COPY' && whichTagging !== 'REPLACE') {
- return { error: errorInstances.InvalidArgument
- .customizeDescription('Unknown tagging directive') };
+ return { error: errorInstances.InvalidArgument.customizeDescription('Unknown tagging directive') };
}
const overrideMetadata = {};
if (headers['x-amz-server-side-encryption']) {
- overrideMetadata['x-amz-server-side-encryption'] =
- headers['x-amz-server-side-encryption'];
+ overrideMetadata['x-amz-server-side-encryption'] = headers['x-amz-server-side-encryption'];
}
- if (headers['x-amz-storage-class']) { // TODO: remove in CLDSRV-639
- overrideMetadata['x-amz-storage-class'] =
- headers['x-amz-storage-class'];
+ if (headers['x-amz-storage-class']) {
+ // TODO: remove in CLDSRV-639
+ overrideMetadata['x-amz-storage-class'] = headers['x-amz-storage-class'];
}
if (headers['x-amz-website-redirect-location']) {
- overrideMetadata['x-amz-website-redirect-location'] =
- headers['x-amz-website-redirect-location'];
+ overrideMetadata['x-amz-website-redirect-location'] = headers['x-amz-website-redirect-location'];
}
- const retentionHeaders = headers['x-amz-object-lock-mode']
- && headers['x-amz-object-lock-retain-until-date'];
+ const retentionHeaders = headers['x-amz-object-lock-mode'] && headers['x-amz-object-lock-retain-until-date'];
const legalHoldHeader = headers['x-amz-object-lock-legal-hold'];
- if ((retentionHeaders || legalHoldHeader)
- && !destBucketMD.isObjectLockEnabled()) {
- return { error: errorInstances.InvalidRequest.customizeDescription(
- 'Bucket is missing ObjectLockConfiguration') };
+ if ((retentionHeaders || legalHoldHeader) && !destBucketMD.isObjectLockEnabled()) {
+ return {
+ error: errorInstances.InvalidRequest.customizeDescription('Bucket is missing ObjectLockConfiguration'),
+ };
}
// Cannot copy from same source and destination if no MD
// changed and no source version id
- if (sourceIsDestination && whichMetadata === 'COPY' &&
- Object.keys(overrideMetadata).length === 0 && !sourceVersionId) {
- return { error: errorInstances.InvalidRequest.customizeDescription('This copy' +
- ' request is illegal because it is trying to copy an ' +
- 'object to itself without changing the object\'s metadata, ' +
- 'storage class, website redirect location or encryption ' +
- 'attributes.') };
+ if (
+ sourceIsDestination &&
+ whichMetadata === 'COPY' &&
+ Object.keys(overrideMetadata).length === 0 &&
+ !sourceVersionId
+ ) {
+ return {
+ error: errorInstances.InvalidRequest.customizeDescription(
+ 'This copy' +
+ ' request is illegal because it is trying to copy an ' +
+ "object to itself without changing the object's metadata, " +
+ 'storage class, website redirect location or encryption ' +
+ 'attributes.',
+ ),
+ };
}
// If COPY, pull all x-amz-meta keys/values from source object
// Otherwise, pull all x-amz-meta keys/values from request headers
- const userMetadata = whichMetadata === 'COPY' ?
- getMetaHeaders(sourceObjMD) :
- getMetaHeaders(headers);
+ const userMetadata = whichMetadata === 'COPY' ? getMetaHeaders(sourceObjMD) : getMetaHeaders(headers);
if (userMetadata instanceof Error) {
log.debug('user metadata validation failed', {
error: userMetadata,
@@ -117,28 +126,23 @@ function _prepMetadata(request, sourceObjMD, headers, sourceIsDestination,
// If metadataDirective is:
// - 'COPY' and source object has a location constraint in its metadata
// we use the bucket destination location constraint
- if (whichMetadata === 'COPY'
- && userMetadata[locationHeader]
- && destBucketMD.getLocationConstraint()) {
+ if (whichMetadata === 'COPY' && userMetadata[locationHeader] && destBucketMD.getLocationConstraint()) {
userMetadata[locationHeader] = destBucketMD.getLocationConstraint();
}
- const backendInfoObjSource = locationConstraintCheck(request,
- sourceObjMD, sourceBucketMD, log);
+ const backendInfoObjSource = locationConstraintCheck(request, sourceObjMD, sourceBucketMD, log);
if (backendInfoObjSource.err) {
return { error: backendInfoObjSource.err };
}
const sourceLocationConstraintName = backendInfoObjSource.controllingLC;
- const backendInfoObjDest = locationConstraintCheck(request,
- userMetadata, destBucketMD, log);
+ const backendInfoObjDest = locationConstraintCheck(request, userMetadata, destBucketMD, log);
if (backendInfoObjDest.err) {
return { error: backendInfoObjDest.err };
}
const destLocationConstraintName = backendInfoObjDest.controllingLC;
// If location constraint header is not included, locations match
- const locationMatch =
- sourceLocationConstraintName === destLocationConstraintName;
+ const locationMatch = sourceLocationConstraintName === destLocationConstraintName;
// If tagging directive is REPLACE but you don't specify any
// tags in the request, the destination object will
@@ -155,8 +159,7 @@ function _prepMetadata(request, sourceObjMD, headers, sourceIsDestination,
// If COPY, pull the necessary headers from source object
// Otherwise, pull them from request headers
- const headersToStoreSource = whichMetadata === 'COPY' ?
- sourceObjMD : headers;
+ const headersToStoreSource = whichMetadata === 'COPY' ? sourceObjMD : headers;
const storeMetadataParams = {
objectKey,
@@ -169,16 +172,14 @@ function _prepMetadata(request, sourceObjMD, headers, sourceIsDestination,
contentMD5: sourceObjMD['content-md5'],
cacheControl: headersToStoreSource['cache-control'],
contentDisposition: headersToStoreSource['content-disposition'],
- contentEncoding:
- removeAWSChunked(headersToStoreSource['content-encoding']),
+ contentEncoding: removeAWSChunked(headersToStoreSource['content-encoding']),
dataStoreName: destLocationConstraintName,
expires: headersToStoreSource.expires,
overrideMetadata,
lastModifiedDate: new Date().toJSON(),
tagging,
taggingCopy,
- replicationInfo: getReplicationInfo(config,
- objectKey, destBucketMD, false, sourceObjMD['content-length']),
+ replicationInfo: getReplicationInfo(config, objectKey, destBucketMD, false, sourceObjMD['content-length']),
locationMatch,
originOp: 's3:ObjectCreated:Copy',
};
@@ -198,8 +199,7 @@ function _prepMetadata(request, sourceObjMD, headers, sourceIsDestination,
storeMetadataParams.bucketOwnerId = destBucketMD.getOwner();
}
- return { storeMetadataParams, sourceLocationConstraintName,
- backendInfoDest: backendInfoObjDest.backendInfo };
+ return { storeMetadataParams, sourceLocationConstraintName, backendInfoDest: backendInfoObjDest.backendInfo };
}
/**
@@ -215,8 +215,7 @@ function _prepMetadata(request, sourceObjMD, headers, sourceIsDestination,
* @param {function} callback - final callback to call with the result
* @return {undefined}
*/
-function objectCopy(authInfo, request, sourceBucket,
- sourceObject, sourceVersionId, log, callback) {
+function objectCopy(authInfo, request, sourceBucket, sourceObject, sourceVersionId, log, callback) {
log.debug('processing request', { method: 'objectCopy' });
const destBucketName = request.bucketName;
const destObjectKey = request.objectKey;
@@ -226,8 +225,7 @@ function objectCopy(authInfo, request, sourceBucket,
return callback(keyLengthError);
}
- const sourceIsDestination =
- destBucketName === sourceBucket && destObjectKey === sourceObject;
+ const sourceIsDestination = destBucketName === sourceBucket && destObjectKey === sourceObject;
const valGetParams = {
authInfo,
bucketName: sourceBucket,
@@ -259,432 +257,574 @@ function objectCopy(authInfo, request, sourceBucket,
namespace: request.namespace,
objectKey: destObjectKey,
};
- const websiteRedirectHeader =
- request.headers['x-amz-website-redirect-location'];
+ const websiteRedirectHeader = request.headers['x-amz-website-redirect-location'];
const responseHeaders = {};
- if (request.headers['x-amz-storage-class'] &&
- !constants.validStorageClasses.includes(request.headers['x-amz-storage-class'])) {
+ if (
+ request.headers['x-amz-storage-class'] &&
+ !constants.validStorageClasses.includes(request.headers['x-amz-storage-class'])
+ ) {
log.trace('invalid storage-class header');
- monitoring.promMetrics('PUT', destBucketName,
- errorInstances.InvalidStorageClass.code, 'copyObject');
+ monitoring.promMetrics('PUT', destBucketName, errorInstances.InvalidStorageClass.code, 'copyObject');
return callback(errors.InvalidStorageClass);
}
if (!validateWebsiteHeader(websiteRedirectHeader)) {
const err = errors.InvalidRedirectLocation;
- log.debug('invalid x-amz-website-redirect-location' +
- `value ${websiteRedirectHeader}`, { error: err });
- monitoring.promMetrics(
- 'PUT', destBucketName, err.code, 'copyObject');
+ log.debug('invalid x-amz-website-redirect-location' + `value ${websiteRedirectHeader}`, { error: err });
+ monitoring.promMetrics('PUT', destBucketName, err.code, 'copyObject');
return callback(err);
}
const queryContainsVersionId = checkQueryVersionId(request.query);
if (queryContainsVersionId instanceof Error) {
return callback(queryContainsVersionId);
}
- return async.waterfall([
- function checkDestAuth(next) {
- return standardMetadataValidateBucketAndObj(valPutParams, request.actionImplicitDenies, log,
- (err, destBucketMD, destObjMD) =>
- updateEncryption(err, destBucketMD, destObjMD, destObjectKey, log, { skipObject: true },
- (err, destBucketMD, destObjMD) => {
- if (err) {
- log.debug('error validating put part of request',
- { error: err });
- return next(err, destBucketMD);
- }
- const flag = destBucketMD.hasDeletedFlag()
- || destBucketMD.hasTransientFlag();
- if (flag) {
- log.trace('deleted flag or transient flag ' +
- 'on destination bucket', { flag });
- return next(errors.NoSuchBucket);
- }
- return next(null, destBucketMD, destObjMD);
- }));
- },
- function checkSourceAuthorization(destBucketMD, destObjMD, next) {
- return standardMetadataValidateBucketAndObj({
- ...valGetParams,
- destObjMD,
- serverAccessLogOptions: { copySource: true },
- }, request.actionImplicitDenies, log,
- (err, sourceBucketMD, sourceObjMD) => {
- if (err) {
- log.debug('error validating get part of request',
- { error: err });
- // eslint-disable-next-line no-param-reassign
- request.sourceServerAccessLog && (request.sourceServerAccessLog.error = err);
- return next(err, null, destBucketMD);
- }
- if (!sourceObjMD) {
- const err = sourceVersionId ? errors.NoSuchVersion :
- errors.NoSuchKey;
- log.debug('no source object', { sourceObject });
- // eslint-disable-next-line no-param-reassign
- request.sourceServerAccessLog && (request.sourceServerAccessLog.error = err);
- return next(err, null, destBucketMD);
- }
- // check if object data is in a cold storage
- const coldErr = verifyColdObjectAvailable(sourceObjMD);
- if (coldErr) {
- // eslint-disable-next-line no-param-reassign
- request.sourceServerAccessLog && (request.sourceServerAccessLog.error = coldErr);
- return next(coldErr, null);
- }
- if (sourceObjMD.isDeleteMarker) {
- log.debug('delete marker on source object',
- { sourceObject });
- let err;
- if (sourceVersionId) {
- err = errorInstances.InvalidRequest
- .customizeDescription('The source of a copy ' +
- 'request may not specifically refer to a delete' +
- 'marker by version id.');
- } else {
- // if user specifies a key in a versioned source bucket
- // without specifying a version, and the object has
- // a delete marker, return NoSuchKey
- err = errors.NoSuchKey;
+ return async.waterfall(
+ [
+ function checkDestAuth(next) {
+ return standardMetadataValidateBucketAndObj(
+ valPutParams,
+ request.actionImplicitDenies,
+ log,
+ (err, destBucketMD, destObjMD) =>
+ updateEncryption(
+ err,
+ destBucketMD,
+ destObjMD,
+ destObjectKey,
+ log,
+ { skipObject: true },
+ (err, destBucketMD, destObjMD) => {
+ if (err) {
+ log.debug('error validating put part of request', { error: err });
+ return next(err, destBucketMD);
+ }
+ const flag = destBucketMD.hasDeletedFlag() || destBucketMD.hasTransientFlag();
+ if (flag) {
+ log.trace('deleted flag or transient flag ' + 'on destination bucket', { flag });
+ return next(errors.NoSuchBucket);
+ }
+ return next(null, destBucketMD, destObjMD);
+ },
+ ),
+ );
+ },
+ function checkSourceAuthorization(destBucketMD, destObjMD, next) {
+ return standardMetadataValidateBucketAndObj(
+ {
+ ...valGetParams,
+ destObjMD,
+ serverAccessLogOptions: { copySource: true },
+ },
+ request.actionImplicitDenies,
+ log,
+ (err, sourceBucketMD, sourceObjMD) => {
+ if (err) {
+ log.debug('error validating get part of request', { error: err });
+ // eslint-disable-next-line no-param-reassign
+ request.sourceServerAccessLog && (request.sourceServerAccessLog.error = err);
+ return next(err, null, destBucketMD);
}
- // eslint-disable-next-line no-param-reassign
- request.sourceServerAccessLog && (request.sourceServerAccessLog.error = err);
- return next(err, destBucketMD);
- }
- const headerValResult =
- validateHeaders(request.headers,
- sourceObjMD['last-modified'],
- sourceObjMD['content-md5']);
- if (headerValResult.error) {
- request.sourceServerAccessLog
+ if (!sourceObjMD) {
+ const err = sourceVersionId ? errors.NoSuchVersion : errors.NoSuchKey;
+ log.debug('no source object', { sourceObject });
// eslint-disable-next-line no-param-reassign
- && (request.sourceServerAccessLog.error = errors.PreconditionFailed);
- return next(errors.PreconditionFailed, destBucketMD);
- }
- const { storeMetadataParams, error: metadataError,
- sourceLocationConstraintName, backendInfoDest } =
- _prepMetadata(request, sourceObjMD, request.headers,
- sourceIsDestination, authInfo, destObjectKey,
- sourceBucketMD, destBucketMD, sourceVersionId, log);
- if (metadataError) {
- // eslint-disable-next-line no-param-reassign
- request.sourceServerAccessLog && (request.sourceServerAccessLog.error = metadataError);
- return next(metadataError, destBucketMD);
- }
- if (storeMetadataParams.metaHeaders) {
- dataStoreContext.metaHeaders =
- storeMetadataParams.metaHeaders;
- }
+ request.sourceServerAccessLog && (request.sourceServerAccessLog.error = err);
+ return next(err, null, destBucketMD);
+ }
+ // check if object data is in a cold storage
+ const coldErr = verifyColdObjectAvailable(sourceObjMD);
+ if (coldErr) {
+ // eslint-disable-next-line no-param-reassign
+ request.sourceServerAccessLog && (request.sourceServerAccessLog.error = coldErr);
+ return next(coldErr, null);
+ }
+ if (sourceObjMD.isDeleteMarker) {
+ log.debug('delete marker on source object', { sourceObject });
+ let err;
+ if (sourceVersionId) {
+ err = errorInstances.InvalidRequest.customizeDescription(
+ 'The source of a copy ' +
+ 'request may not specifically refer to a delete' +
+ 'marker by version id.',
+ );
+ } else {
+ // if user specifies a key in a versioned source bucket
+ // without specifying a version, and the object has
+ // a delete marker, return NoSuchKey
+ err = errors.NoSuchKey;
+ }
+ // eslint-disable-next-line no-param-reassign
+ request.sourceServerAccessLog && (request.sourceServerAccessLog.error = err);
+ return next(err, destBucketMD);
+ }
+ const sourceSize = parseInt(sourceObjMD['content-length'], 10);
+ if (sourceSize > constants.maximumAllowedUploadSize && !config.bypassMaxPutObjectSize) {
+ log.debug('copy source object too large', { sourceSize });
+ const err = errorInstances.InvalidRequest.customizeDescription(
+ 'The specified copy source is larger than the maximum ' +
+ `allowable size for a copy source: ${constants.maximumAllowedUploadSize}`,
+ );
+ // eslint-disable-next-line no-param-reassign
+ request.sourceServerAccessLog && (request.sourceServerAccessLog.error = err);
+ return next(err, destBucketMD);
+ }
+ const headerValResult = validateHeaders(
+ request.headers,
+ sourceObjMD['last-modified'],
+ sourceObjMD['content-md5'],
+ );
+ if (headerValResult.error) {
+ request.sourceServerAccessLog &&
+ // eslint-disable-next-line no-param-reassign
+ (request.sourceServerAccessLog.error = errors.PreconditionFailed);
+ return next(errors.PreconditionFailed, destBucketMD);
+ }
+ const {
+ storeMetadataParams,
+ error: metadataError,
+ sourceLocationConstraintName,
+ backendInfoDest,
+ } = _prepMetadata(
+ request,
+ sourceObjMD,
+ request.headers,
+ sourceIsDestination,
+ authInfo,
+ destObjectKey,
+ sourceBucketMD,
+ destBucketMD,
+ sourceVersionId,
+ log,
+ );
+ if (metadataError) {
+ // eslint-disable-next-line no-param-reassign
+ request.sourceServerAccessLog && (request.sourceServerAccessLog.error = metadataError);
+ return next(metadataError, destBucketMD);
+ }
+ if (storeMetadataParams.metaHeaders) {
+ dataStoreContext.metaHeaders = storeMetadataParams.metaHeaders;
+ }
- storeMetadataParams.overheadField = constants.overheadField;
-
- let dataLocator;
- // If 0 byte object just set dataLocator to empty array
- if (!sourceObjMD.location) {
- dataLocator = [];
- } else {
- // To provide for backwards compatibility before
- // md-model-version 2, need to handle cases where
- // objMD.location is just a string
- dataLocator = Array.isArray(sourceObjMD.location) ?
- sourceObjMD.location : [{ key: sourceObjMD.location }];
- }
+ storeMetadataParams.overheadField = constants.overheadField;
- if (sourceObjMD['x-amz-server-side-encryption']) {
- for (let i = 0; i < dataLocator.length; i++) {
- dataLocator[i].masterKeyId = sourceObjMD[
- 'x-amz-server-side-encryption-aws-kms-key-id'];
- dataLocator[i].algorithm =
- sourceObjMD['x-amz-server-side-encryption'];
+ let dataLocator;
+ // If 0 byte object just set dataLocator to empty array
+ if (!sourceObjMD.location) {
+ dataLocator = [];
+ } else {
+ // To provide for backwards compatibility before
+ // md-model-version 2, need to handle cases where
+ // objMD.location is just a string
+ dataLocator = Array.isArray(sourceObjMD.location)
+ ? sourceObjMD.location
+ : [{ key: sourceObjMD.location }];
+ }
+
+ if (sourceObjMD['x-amz-server-side-encryption']) {
+ for (let i = 0; i < dataLocator.length; i++) {
+ dataLocator[i].masterKeyId = sourceObjMD['x-amz-server-side-encryption-aws-kms-key-id'];
+ dataLocator[i].algorithm = sourceObjMD['x-amz-server-side-encryption'];
+ }
}
- }
- // If the destination key already exists
- if (destObjMD) {
- // Re-use creation-time if we can
- if (destObjMD['creation-time']) {
- storeMetadataParams.creationTime =
- destObjMD['creation-time'];
- // Otherwise fallback to last-modified
+ // If the destination key already exists
+ if (destObjMD) {
+ // Re-use creation-time if we can
+ if (destObjMD['creation-time']) {
+ storeMetadataParams.creationTime = destObjMD['creation-time'];
+ // Otherwise fallback to last-modified
+ } else {
+ storeMetadataParams.creationTime = destObjMD['last-modified'];
+ }
+ // If this is a new key, create a new timestamp
} else {
- storeMetadataParams.creationTime =
- destObjMD['last-modified'];
+ storeMetadataParams.creationTime = new Date().toJSON();
}
- // If this is a new key, create a new timestamp
- } else {
- storeMetadataParams.creationTime = new Date().toJSON();
- }
- return next(null, storeMetadataParams, dataLocator,
- sourceBucketMD, destBucketMD, destObjMD,
- sourceLocationConstraintName, backendInfoDest);
- });
- },
- function getSSEConfiguration(storeMetadataParams, dataLocator, sourceBucketMD,
- destBucketMD, destObjMD, sourceLocationConstraintName,
- backendInfoDest, next) {
- getObjectSSEConfiguration(
- request.headers,
- destBucketMD,
- log,
- (err, sseConfig) =>
- next(err, storeMetadataParams, dataLocator, sourceBucketMD,
- destBucketMD, destObjMD, sourceLocationConstraintName,
- backendInfoDest, sseConfig));
- },
- function goGetData(storeMetadataParams, dataLocator, sourceBucketMD,
- destBucketMD, destObjMD, sourceLocationConstraintName,
- backendInfoDest, serverSideEncryption, next) {
- const vcfg = destBucketMD.getVersioningConfiguration();
- const isVersionedObj = vcfg && vcfg.Status === 'Enabled';
- const destLocationConstraintName =
- storeMetadataParams.dataStoreName;
- const needsEncryption = serverSideEncryption && !!serverSideEncryption.algo;
- // skip if source and dest and location constraint the same and
- // versioning is not enabled
- // still send along serverSideEncryption info so algo
- // and masterKeyId stored properly in metadata
- if (sourceIsDestination && storeMetadataParams.locationMatch
- && !isVersionedObj && !needsEncryption) {
- return next(null, storeMetadataParams, dataLocator, destObjMD,
- serverSideEncryption, destBucketMD);
- }
+ return next(
+ null,
+ storeMetadataParams,
+ dataLocator,
+ sourceBucketMD,
+ destBucketMD,
+ destObjMD,
+ sourceLocationConstraintName,
+ backendInfoDest,
+ );
+ },
+ );
+ },
+ function getSSEConfiguration(
+ storeMetadataParams,
+ dataLocator,
+ sourceBucketMD,
+ destBucketMD,
+ destObjMD,
+ sourceLocationConstraintName,
+ backendInfoDest,
+ next,
+ ) {
+ getObjectSSEConfiguration(request.headers, destBucketMD, log, (err, sseConfig) =>
+ next(
+ err,
+ storeMetadataParams,
+ dataLocator,
+ sourceBucketMD,
+ destBucketMD,
+ destObjMD,
+ sourceLocationConstraintName,
+ backendInfoDest,
+ sseConfig,
+ ),
+ );
+ },
+ function goGetData(
+ storeMetadataParams,
+ dataLocator,
+ sourceBucketMD,
+ destBucketMD,
+ destObjMD,
+ sourceLocationConstraintName,
+ backendInfoDest,
+ serverSideEncryption,
+ next,
+ ) {
+ const vcfg = destBucketMD.getVersioningConfiguration();
+ const isVersionedObj = vcfg && vcfg.Status === 'Enabled';
+ const destLocationConstraintName = storeMetadataParams.dataStoreName;
+ const needsEncryption = serverSideEncryption && !!serverSideEncryption.algo;
+ // skip if source and dest and location constraint the same and
+ // versioning is not enabled
+ // still send along serverSideEncryption info so algo
+ // and masterKeyId stored properly in metadata
+ if (sourceIsDestination && storeMetadataParams.locationMatch && !isVersionedObj && !needsEncryption) {
+ return next(null, storeMetadataParams, dataLocator, destObjMD, serverSideEncryption, destBucketMD);
+ }
- // also skip if 0 byte object, unless location constraint is an
- // external backend and differs from source, in which case put
- // metadata to backend
- let destLocationConstraintType;
- if (config.backends.data === 'multiple') {
- destLocationConstraintType =
- config.getLocationConstraintType(destLocationConstraintName);
- }
- if (destLocationConstraintType &&
- versioningNotImplBackends[destLocationConstraintType]
- && isVersionedObj) {
- log.debug(externalVersioningErrorMessage,
- { method: 'multipleBackendGateway',
- error: errors.NotImplemented });
- return next(errorInstances.NotImplemented.customizeDescription(
- externalVersioningErrorMessage), destBucketMD);
- }
- if (dataLocator.length === 0) {
- if (!storeMetadataParams.locationMatch &&
- destLocationConstraintType &&
- constants.externalBackends[destLocationConstraintType]) {
- return data.put(null, null, storeMetadataParams.size,
- dataStoreContext, backendInfoDest,
- log, (error, objectRetrievalInfo) => {
- if (error) {
- return next(error, destBucketMD);
- }
- const putResult = {
- key: objectRetrievalInfo.key,
- dataStoreName: objectRetrievalInfo.
- dataStoreName,
- dataStoreType: objectRetrievalInfo.
- dataStoreType,
- size: storeMetadataParams.size,
- };
- const putResultArr = [putResult];
- return next(null, storeMetadataParams, putResultArr,
- destObjMD, serverSideEncryption, destBucketMD);
- });
+ // also skip if 0 byte object, unless location constraint is an
+ // external backend and differs from source, in which case put
+ // metadata to backend
+ let destLocationConstraintType;
+ if (config.backends.data === 'multiple') {
+ destLocationConstraintType = config.getLocationConstraintType(destLocationConstraintName);
}
- return next(null, storeMetadataParams, dataLocator, destObjMD,
- serverSideEncryption, destBucketMD);
- }
- const originalIdentityImpDenies = request.actionImplicitDenies;
- // eslint-disable-next-line no-param-reassign
- delete request.actionImplicitDenies;
- return data.copyObject(request, sourceLocationConstraintName,
- storeMetadataParams, dataLocator, dataStoreContext,
- backendInfoDest, sourceBucketMD, destBucketMD, serverSideEncryption, log,
- (err, results) => {
- // eslint-disable-next-line no-param-reassign
- request.actionImplicitDenies = originalIdentityImpDenies;
- if (err) {
- // eslint-disable-next-line no-param-reassign
- request.sourceServerAccessLog && (request.sourceServerAccessLog.error = err);
- return next(err, destBucketMD);
+ if (
+ destLocationConstraintType &&
+ versioningNotImplBackends[destLocationConstraintType] &&
+ isVersionedObj
+ ) {
+ log.debug(externalVersioningErrorMessage, {
+ method: 'multipleBackendGateway',
+ error: errors.NotImplemented,
+ });
+ return next(
+ errorInstances.NotImplemented.customizeDescription(externalVersioningErrorMessage),
+ destBucketMD,
+ );
}
- return next(null, storeMetadataParams, results,
- destObjMD, serverSideEncryption, destBucketMD);
- });
- },
- function getVersioningInfo(storeMetadataParams, destDataGetInfoArr,
- destObjMD, serverSideEncryption, destBucketMD, next) {
- if (!destBucketMD.isVersioningEnabled() && destObjMD?.archive?.archiveInfo) {
- // Ensure we trigger a "delete" event in the oplog for the previously archived object
- // eslint-disable-next-line
- storeMetadataParams.needOplogUpdate = 's3:ReplaceArchivedObject';
- }
- return versioningPreprocessing(destBucketName,
- destBucketMD, destObjectKey, destObjMD, log,
- (err, options) => {
- if (err) {
- log.debug('error processing versioning info',
- { error: err });
- return next(err, null, destBucketMD);
+ if (dataLocator.length === 0) {
+ if (
+ !storeMetadataParams.locationMatch &&
+ destLocationConstraintType &&
+ constants.externalBackends[destLocationConstraintType]
+ ) {
+ return data.put(
+ null,
+ null,
+ storeMetadataParams.size,
+ dataStoreContext,
+ backendInfoDest,
+ log,
+ (error, objectRetrievalInfo) => {
+ if (error) {
+ return next(error, destBucketMD);
+ }
+ const putResult = {
+ key: objectRetrievalInfo.key,
+ dataStoreName: objectRetrievalInfo.dataStoreName,
+ dataStoreType: objectRetrievalInfo.dataStoreType,
+ size: storeMetadataParams.size,
+ };
+ const putResultArr = [putResult];
+ return next(
+ null,
+ storeMetadataParams,
+ putResultArr,
+ destObjMD,
+ serverSideEncryption,
+ destBucketMD,
+ );
+ },
+ );
}
+ return next(null, storeMetadataParams, dataLocator, destObjMD, serverSideEncryption, destBucketMD);
+ }
+ const originalIdentityImpDenies = request.actionImplicitDenies;
+ // eslint-disable-next-line no-param-reassign
+ delete request.actionImplicitDenies;
+ return data.copyObject(
+ request,
+ sourceLocationConstraintName,
+ storeMetadataParams,
+ dataLocator,
+ dataStoreContext,
+ backendInfoDest,
+ sourceBucketMD,
+ destBucketMD,
+ serverSideEncryption,
+ log,
+ (err, results) => {
+ // eslint-disable-next-line no-param-reassign
+ request.actionImplicitDenies = originalIdentityImpDenies;
+ if (err) {
+ // eslint-disable-next-line no-param-reassign
+ request.sourceServerAccessLog && (request.sourceServerAccessLog.error = err);
+ return next(err, destBucketMD);
+ }
+ return next(null, storeMetadataParams, results, destObjMD, serverSideEncryption, destBucketMD);
+ },
+ );
+ },
+ function getVersioningInfo(
+ storeMetadataParams,
+ destDataGetInfoArr,
+ destObjMD,
+ serverSideEncryption,
+ destBucketMD,
+ next,
+ ) {
+ if (!destBucketMD.isVersioningEnabled() && destObjMD?.archive?.archiveInfo) {
+ // Ensure we trigger a "delete" event in the oplog for the previously archived object
+ // eslint-disable-next-line
+ storeMetadataParams.needOplogUpdate = 's3:ReplaceArchivedObject';
+ }
+ return versioningPreprocessing(
+ destBucketName,
+ destBucketMD,
+ destObjectKey,
+ destObjMD,
+ log,
+ (err, options) => {
+ if (err) {
+ log.debug('error processing versioning info', { error: err });
+ return next(err, null, destBucketMD);
+ }
- const location = destDataGetInfoArr?.[0]?.dataStoreName;
- if (location === destBucketMD.getLocationConstraint() && destBucketMD.isIngestionBucket()) {
- // If the object is being written to the "ingested" storage location, keep the same
- // versionId for consistency and to avoid creating an extra version when it gets
- // ingested
- const backendVersionId = decodeVID(destDataGetInfoArr[0].dataStoreVersionId);
- if (!(backendVersionId instanceof Error)) {
- options.versionId = backendVersionId; // eslint-disable-line no-param-reassign
+ const location = destDataGetInfoArr?.[0]?.dataStoreName;
+ if (location === destBucketMD.getLocationConstraint() && destBucketMD.isIngestionBucket()) {
+ // If the object is being written to the "ingested" storage location, keep the same
+ // versionId for consistency and to avoid creating an extra version when it gets
+ // ingested
+ const backendVersionId = decodeVID(destDataGetInfoArr[0].dataStoreVersionId);
+ if (!(backendVersionId instanceof Error)) {
+ options.versionId = backendVersionId; // eslint-disable-line no-param-reassign
+ }
}
- }
+ // eslint-disable-next-line
+ storeMetadataParams.versionId = options.versionId;
+ // eslint-disable-next-line
+ storeMetadataParams.versioning = options.versioning;
+ // eslint-disable-next-line
+ storeMetadataParams.isNull = options.isNull;
+ if (options.extraMD) {
+ Object.assign(storeMetadataParams, options.extraMD);
+ }
+ const dataToDelete = options.dataToDelete;
+ return next(
+ null,
+ storeMetadataParams,
+ destDataGetInfoArr,
+ destObjMD,
+ serverSideEncryption,
+ destBucketMD,
+ dataToDelete,
+ );
+ },
+ );
+ },
+ function storeNewMetadata(
+ storeMetadataParams,
+ destDataGetInfoArr,
+ destObjMD,
+ serverSideEncryption,
+ destBucketMD,
+ dataToDelete,
+ next,
+ ) {
+ if (destObjMD && destObjMD.uploadId) {
// eslint-disable-next-line
- storeMetadataParams.versionId = options.versionId;
- // eslint-disable-next-line
- storeMetadataParams.versioning = options.versioning;
- // eslint-disable-next-line
- storeMetadataParams.isNull = options.isNull;
- if (options.extraMD) {
- Object.assign(storeMetadataParams, options.extraMD);
- }
- const dataToDelete = options.dataToDelete;
- return next(null, storeMetadataParams, destDataGetInfoArr,
- destObjMD, serverSideEncryption, destBucketMD,
- dataToDelete);
- });
- },
- function storeNewMetadata(storeMetadataParams, destDataGetInfoArr,
- destObjMD, serverSideEncryption, destBucketMD, dataToDelete, next) {
- if (destObjMD && destObjMD.uploadId) {
- // eslint-disable-next-line
- storeMetadataParams.oldReplayId = destObjMD.uploadId;
- }
+ storeMetadataParams.oldReplayId = destObjMD.uploadId;
+ }
- return services.metadataStoreObject(destBucketName,
- destDataGetInfoArr, serverSideEncryption,
- storeMetadataParams, (err, result) => {
- if (err) {
- log.debug('error storing new metadata', { error: err });
- return next(err, null, destBucketMD);
- }
- const sourceObjSize = storeMetadataParams.size;
- const destObjPrevSize = (destObjMD &&
- destObjMD['content-length'] !== undefined) ?
- destObjMD['content-length'] : null;
-
- setExpirationHeaders(responseHeaders, {
- lifecycleConfig: destBucketMD.getLifecycleConfiguration(),
- objectParams: {
- key: destObjectKey,
- date: result.lastModified,
- tags: result.tags,
- },
- });
+ return services.metadataStoreObject(
+ destBucketName,
+ destDataGetInfoArr,
+ serverSideEncryption,
+ storeMetadataParams,
+ (err, result) => {
+ if (err) {
+ log.debug('error storing new metadata', { error: err });
+ return next(err, null, destBucketMD);
+ }
+ const sourceObjSize = storeMetadataParams.size;
+ const destObjPrevSize =
+ destObjMD && destObjMD['content-length'] !== undefined ? destObjMD['content-length'] : null;
+
+ setExpirationHeaders(responseHeaders, {
+ lifecycleConfig: destBucketMD.getLifecycleConfiguration(),
+ objectParams: {
+ key: destObjectKey,
+ date: result.lastModified,
+ tags: result.tags,
+ },
+ });
- return next(null, dataToDelete, result, destBucketMD,
- storeMetadataParams, serverSideEncryption,
- sourceObjSize, destObjPrevSize);
- });
- },
- function deleteExistingData(dataToDelete, storingNewMdResult,
- destBucketMD, storeMetadataParams, serverSideEncryption,
- sourceObjSize, destObjPrevSize, next) {
- // Clean up any potential orphans in data if object
- // put is an overwrite of already existing
- // object with same name, so long as the source is not
- // the same as the destination
- if (!sourceIsDestination && dataToDelete) {
- const newDataStoreName = storeMetadataParams.dataStoreName;
- return data.batchDelete(dataToDelete, request.method,
- newDataStoreName, log, err => {
+ return next(
+ null,
+ dataToDelete,
+ result,
+ destBucketMD,
+ storeMetadataParams,
+ serverSideEncryption,
+ sourceObjSize,
+ destObjPrevSize,
+ );
+ },
+ );
+ },
+ function deleteExistingData(
+ dataToDelete,
+ storingNewMdResult,
+ destBucketMD,
+ storeMetadataParams,
+ serverSideEncryption,
+ sourceObjSize,
+ destObjPrevSize,
+ next,
+ ) {
+ // Clean up any potential orphans in data if object
+ // put is an overwrite of already existing
+ // object with same name, so long as the source is not
+ // the same as the destination
+ if (!sourceIsDestination && dataToDelete) {
+ const newDataStoreName = storeMetadataParams.dataStoreName;
+ return data.batchDelete(dataToDelete, request.method, newDataStoreName, log, err => {
if (err) {
// if error, log the error and move on as it is not
// relevant to the client as the client's
// object already succeeded putting data, metadata
- log.error('error deleting existing data',
- { error: err });
+ log.error('error deleting existing data', { error: err });
}
- next(null,
- storingNewMdResult, destBucketMD, storeMetadataParams,
- serverSideEncryption, sourceObjSize, destObjPrevSize);
+ next(
+ null,
+ storingNewMdResult,
+ destBucketMD,
+ storeMetadataParams,
+ serverSideEncryption,
+ sourceObjSize,
+ destObjPrevSize,
+ );
});
+ }
+ return next(
+ null,
+ storingNewMdResult,
+ destBucketMD,
+ storeMetadataParams,
+ serverSideEncryption,
+ sourceObjSize,
+ destObjPrevSize,
+ );
+ },
+ ],
+ (
+ err,
+ storingNewMdResult,
+ destBucketMD,
+ storeMetadataParams,
+ serverSideEncryption,
+ sourceObjSize,
+ destObjPrevSize,
+ ) => {
+ const corsHeaders = collectCorsHeaders(request.headers.origin, request.method, destBucketMD);
+
+ // Store full object size for server access logs
+ if (request.serverAccessLog) {
+ // eslint-disable-next-line no-param-reassign
+ request.serverAccessLog.objectSize = sourceObjSize;
}
- return next(null,
- storingNewMdResult, destBucketMD, storeMetadataParams,
- serverSideEncryption, sourceObjSize, destObjPrevSize);
- },
- ], (err, storingNewMdResult, destBucketMD, storeMetadataParams,
- serverSideEncryption, sourceObjSize, destObjPrevSize) => {
- const corsHeaders = collectCorsHeaders(request.headers.origin,
- request.method, destBucketMD);
-
- // Store full object size for server access logs
- if (request.serverAccessLog) {
- // eslint-disable-next-line no-param-reassign
- request.serverAccessLog.objectSize = sourceObjSize;
- }
-
- // Initialize the queue for internal log request logging
- initializeInternalLogRequestQueue(request);
- // Queue the source-side access log (REST.COPY.OBJECT_GET)
- queueInternalLogRequest(request, {
- operation: 'REST.COPY.OBJECT_GET',
- sourceBucket,
- sourceObject,
- objectSize: sourceObjSize || null,
- });
- if (err) {
+ // Initialize the queue for internal log request logging
+ initializeInternalLogRequestQueue(request);
+ // Queue the source-side access log (REST.COPY.OBJECT_GET)
+ queueInternalLogRequest(request, {
+ operation: 'REST.COPY.OBJECT_GET',
+ sourceBucket,
+ sourceObject,
+ objectSize: sourceObjSize || null,
+ });
+
+ if (err) {
+ monitoring.promMetrics('PUT', destBucketName, err.code, 'copyObject');
+ return callback(err, null, corsHeaders);
+ }
+ const xml = [
+ '',
+ '',
+ '',
+ new Date(storeMetadataParams.lastModifiedDate).toISOString(),
+ '',
+ '"',
+ storeMetadataParams.contentMD5,
+ '"',
+ '',
+ ].join('');
+ const additionalHeaders = corsHeaders || {};
+ if (serverSideEncryption) {
+ setSSEHeaders(
+ additionalHeaders,
+ serverSideEncryption.algorithm,
+ serverSideEncryption.configuredMasterKeyId || serverSideEncryption.masterKeyId,
+ );
+ }
+ if (sourceVersionId) {
+ additionalHeaders['x-amz-copy-source-version-id'] = versionIdUtils.encode(sourceVersionId);
+ }
+ const isVersioned = storingNewMdResult && storingNewMdResult.versionId;
+ if (isVersioned) {
+ additionalHeaders['x-amz-version-id'] = versionIdUtils.encode(storingNewMdResult.versionId);
+ }
+
+ Object.assign(responseHeaders, additionalHeaders);
+
+ // Only pre-existing non-versioned objects get 0 all others use 1
+ const numberOfObjects = !isVersioned && destObjPrevSize !== null ? 0 : 1;
+
+ pushMetric('copyObject', log, {
+ authInfo,
+ canonicalID: destBucketMD.getOwner(),
+ bucket: destBucketName,
+ keys: [destObjectKey],
+ newByteLength: sourceObjSize,
+ oldByteLength: isVersioned ? null : destObjPrevSize,
+ location: storeMetadataParams.dataStoreName,
+ versionId: isVersioned ? storingNewMdResult.versionId : undefined,
+ numberOfObjects,
+ });
monitoring.promMetrics(
- 'PUT', destBucketName, err.code, 'copyObject');
- return callback(err, null, corsHeaders);
- }
- const xml = [
- '',
- '',
- '', new Date(storeMetadataParams.lastModifiedDate)
- .toISOString(), '',
- '"', storeMetadataParams.contentMD5, '"',
- '',
- ].join('');
- const additionalHeaders = corsHeaders || {};
- if (serverSideEncryption) {
- setSSEHeaders(additionalHeaders,
- serverSideEncryption.algorithm,
- serverSideEncryption.configuredMasterKeyId || serverSideEncryption.masterKeyId
+ 'PUT',
+ destBucketName,
+ '200',
+ 'copyObject',
+ sourceObjSize,
+ destObjPrevSize,
+ isVersioned,
);
- }
- if (sourceVersionId) {
- additionalHeaders['x-amz-copy-source-version-id'] =
- versionIdUtils.encode(sourceVersionId);
- }
- const isVersioned = storingNewMdResult && storingNewMdResult.versionId;
- if (isVersioned) {
- additionalHeaders['x-amz-version-id'] =
- versionIdUtils.encode(storingNewMdResult.versionId);
- }
-
- Object.assign(responseHeaders, additionalHeaders);
-
- // Only pre-existing non-versioned objects get 0 all others use 1
- const numberOfObjects = !isVersioned && destObjPrevSize !== null ? 0 : 1;
-
- pushMetric('copyObject', log, {
- authInfo,
- canonicalID: destBucketMD.getOwner(),
- bucket: destBucketName,
- keys: [destObjectKey],
- newByteLength: sourceObjSize,
- oldByteLength: isVersioned ? null : destObjPrevSize,
- location: storeMetadataParams.dataStoreName,
- versionId: isVersioned ? storingNewMdResult.versionId : undefined,
- numberOfObjects,
- });
- monitoring.promMetrics('PUT', destBucketName, '200',
- 'copyObject', sourceObjSize, destObjPrevSize, isVersioned);
- // Add expiration header if lifecycle enabled
- return callback(null, xml, responseHeaders);
- });
+ // Add expiration header if lifecycle enabled
+ return callback(null, xml, responseHeaders);
+ },
+ );
}
module.exports = objectCopy;
diff --git a/tests/unit/api/objectCopy.js b/tests/unit/api/objectCopy.js
index 2448f3276f..526bef3fb8 100644
--- a/tests/unit/api/objectCopy.js
+++ b/tests/unit/api/objectCopy.js
@@ -9,18 +9,16 @@ const bucketPutPolicy = require('../../../lib/api/bucketPutPolicy');
const objectPut = require('../../../lib/api/objectPut');
const objectCopy = require('../../../lib/api/objectCopy');
const DummyRequest = require('../DummyRequest');
-const { cleanup, DummyRequestLogger, makeAuthInfo, versioningTestUtils }
- = require('../helpers');
+const { cleanup, DummyRequestLogger, makeAuthInfo, versioningTestUtils } = require('../helpers');
const mpuUtils = require('../utils/mpuUtils');
const metadata = require('../metadataswitch');
const { data } = require('../../../lib/data/wrapper');
-const { objectLocationConstraintHeader } = require('../../../constants');
+const constants = require('../../../constants');
+const { objectLocationConstraintHeader } = constants;
const { fakeMetadataArchive } = require('../../functional/aws-node-sdk/test/utils/init');
const { config } = require('../../../lib/Config');
-const {
- LOCATION_NAME_CRR,
-} = require('../../constants');
+const { LOCATION_NAME_CRR } = require('../../constants');
const any = sinon.match.any;
@@ -58,50 +56,41 @@ function _createObjectCopyRequest(destBucketName, headers = {}) {
const putDestBucketRequest = _createBucketPutRequest(destBucketName);
const putSourceBucketRequest = _createBucketPutRequest(sourceBucketName);
-const enableVersioningRequest = versioningTestUtils
- .createBucketPutVersioningReq(destBucketName, 'Enabled');
-const suspendVersioningRequest = versioningTestUtils
- .createBucketPutVersioningReq(destBucketName, 'Suspended');
-const objData = ['foo0', 'foo1', 'foo2'].map(str =>
- Buffer.from(str, 'utf8'));
-
+const enableVersioningRequest = versioningTestUtils.createBucketPutVersioningReq(destBucketName, 'Enabled');
+const suspendVersioningRequest = versioningTestUtils.createBucketPutVersioningReq(destBucketName, 'Suspended');
+const objData = ['foo0', 'foo1', 'foo2'].map(str => Buffer.from(str, 'utf8'));
describe('objectCopy with versioning', () => {
- const testPutObjectRequests = objData.slice(0, 2).map(data =>
- versioningTestUtils.createPutObjectRequest(destBucketName, objectKey,
- data));
- testPutObjectRequests.push(versioningTestUtils
- .createPutObjectRequest(sourceBucketName, objectKey, objData[2]));
+ const testPutObjectRequests = objData
+ .slice(0, 2)
+ .map(data => versioningTestUtils.createPutObjectRequest(destBucketName, objectKey, data));
+ testPutObjectRequests.push(versioningTestUtils.createPutObjectRequest(sourceBucketName, objectKey, objData[2]));
before(done => {
cleanup();
sinon.spy(metadata, 'putObjectMD');
- async.series([
- callback => bucketPut(authInfo, putDestBucketRequest, log,
- callback),
- callback => bucketPut(authInfo, putSourceBucketRequest, log,
- callback),
- // putting null version: put obj before versioning configured
- // in dest bucket
- callback => objectPut(authInfo, testPutObjectRequests[0],
- undefined, log, callback),
- callback => bucketPutVersioning(authInfo,
- enableVersioningRequest, log, callback),
- // put another version in dest bucket:
- callback => objectPut(authInfo, testPutObjectRequests[1],
- undefined, log, callback),
- callback => bucketPutVersioning(authInfo,
- suspendVersioningRequest, log, callback),
- // put source object in source bucket
- callback => objectPut(authInfo, testPutObjectRequests[2],
- undefined, log, callback),
- ], err => {
- if (err) {
- return done(err);
- }
- versioningTestUtils.assertDataStoreValues(ds, objData);
- return done();
- });
+ async.series(
+ [
+ callback => bucketPut(authInfo, putDestBucketRequest, log, callback),
+ callback => bucketPut(authInfo, putSourceBucketRequest, log, callback),
+ // putting null version: put obj before versioning configured
+ // in dest bucket
+ callback => objectPut(authInfo, testPutObjectRequests[0], undefined, log, callback),
+ callback => bucketPutVersioning(authInfo, enableVersioningRequest, log, callback),
+ // put another version in dest bucket:
+ callback => objectPut(authInfo, testPutObjectRequests[1], undefined, log, callback),
+ callback => bucketPutVersioning(authInfo, suspendVersioningRequest, log, callback),
+ // put source object in source bucket
+ callback => objectPut(authInfo, testPutObjectRequests[2], undefined, log, callback),
+ ],
+ err => {
+ if (err) {
+ return done(err);
+ }
+ versioningTestUtils.assertDataStoreValues(ds, objData);
+ return done();
+ },
+ );
});
after(() => {
@@ -109,13 +98,14 @@ describe('objectCopy with versioning', () => {
cleanup();
});
- it('should delete null version when creating new null version, ' +
- 'even when null version is not the latest version', done => {
- // will have another copy of last object in datastore after objectCopy
- const expectedValues = [undefined, objData[1], objData[2], objData[2]];
- const testObjectCopyRequest = _createObjectCopyRequest(destBucketName);
- objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey,
- undefined, log, err => {
+ it(
+ 'should delete null version when creating new null version, ' +
+ 'even when null version is not the latest version',
+ done => {
+ // will have another copy of last object in datastore after objectCopy
+ const expectedValues = [undefined, objData[1], objData[2], objData[2]];
+ const testObjectCopyRequest = _createObjectCopyRequest(destBucketName);
+ objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, err => {
assert.ifError(err, `Unexpected err: ${err}`);
sinon.assert.calledWith(
metadata.putObjectMD.lastCall,
@@ -124,44 +114,42 @@ describe('objectCopy with versioning', () => {
sinon.match({ _data: { originOp: 's3:ObjectCreated:Copy' } }),
sinon.match.any,
sinon.match.any,
- sinon.match.any
+ sinon.match.any,
);
setImmediate(() => {
- versioningTestUtils
- .assertDataStoreValues(ds, expectedValues);
+ versioningTestUtils.assertDataStoreValues(ds, expectedValues);
done();
});
});
- });
+ },
+ );
it('should not copy object with storage-class header not equal to STANDARD', done => {
const testObjectCopyRequest = _createObjectCopyRequest(destBucketName);
testObjectCopyRequest.headers['x-amz-storage-class'] = 'COLD';
- objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey,
- undefined, log, err => {
- setImmediate(() => {
- assert.strictEqual(err.is.InvalidStorageClass, true);
- done();
- });
+ objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, err => {
+ setImmediate(() => {
+ assert.strictEqual(err.is.InvalidStorageClass, true);
+ done();
});
+ });
});
it('should not set bucketOwnerId if requesting account owns dest bucket', done => {
const testObjectCopyRequest = _createObjectCopyRequest(destBucketName);
- objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey,
- undefined, log, err => {
- assert.ifError(err);
- sinon.assert.calledWith(
- metadata.putObjectMD.lastCall,
- destBucketName,
- objectKey,
- sinon.match({ _data: { bucketOwnerId: sinon.match.typeOf('undefined') } }),
- sinon.match.any,
- sinon.match.any,
- sinon.match.any
- );
- done();
- });
+ objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, err => {
+ assert.ifError(err);
+ sinon.assert.calledWith(
+ metadata.putObjectMD.lastCall,
+ destBucketName,
+ objectKey,
+ sinon.match({ _data: { bucketOwnerId: sinon.match.typeOf('undefined') } }),
+ sinon.match.any,
+ sinon.match.any,
+ sinon.match.any,
+ );
+ done();
+ });
});
// TODO: S3C-9965
@@ -184,9 +172,7 @@ describe('objectCopy with versioning', () => {
Effect: 'Allow',
Principal: { AWS: `arn:aws:iam::${authInfo2.shortid}:root` },
Action: ['s3:GetObject'],
- Resource: [
- `arn:aws:s3:::${sourceBucketName}/*`,
- ],
+ Resource: [`arn:aws:s3:::${sourceBucketName}/*`],
},
],
}),
@@ -205,9 +191,7 @@ describe('objectCopy with versioning', () => {
Effect: 'Allow',
Principal: { AWS: `arn:aws:iam::${authInfo2.shortid}:root` },
Action: ['s3:PutObject'],
- Resource: [
- `arn:aws:s3:::${destBucketName}/*`,
- ],
+ Resource: [`arn:aws:s3:::${destBucketName}/*`],
},
],
}),
@@ -216,51 +200,47 @@ describe('objectCopy with versioning', () => {
assert.ifError(err);
bucketPutPolicy(authInfo, testPutDestPolicyRequest, log, err => {
assert.ifError(err);
- objectCopy(authInfo2, testObjectCopyRequest, sourceBucketName, objectKey,
- undefined, log, err => {
- sinon.assert.calledWith(
- metadata.putObjectMD.lastCall,
- destBucketName,
- objectKey,
- sinon.match({ _data: { bucketOwnerId: authInfo.canonicalID } }),
- sinon.match.any,
- sinon.match.any,
- sinon.match.any
- );
- assert.ifError(err);
- done();
- });
+ objectCopy(authInfo2, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, err => {
+ sinon.assert.calledWith(
+ metadata.putObjectMD.lastCall,
+ destBucketName,
+ objectKey,
+ sinon.match({ _data: { bucketOwnerId: authInfo.canonicalID } }),
+ sinon.match.any,
+ sinon.match.any,
+ sinon.match.any,
+ );
+ assert.ifError(err);
+ done();
+ });
});
});
});
});
describe('non-versioned objectCopy', () => {
- const testPutObjectRequest = versioningTestUtils
- .createPutObjectRequest(sourceBucketName, objectKey, objData[0]);
- const testPutDestObjectRequest = versioningTestUtils
- .createPutObjectRequest(destBucketName, objectKey, objData[1]);
+ const testPutObjectRequest = versioningTestUtils.createPutObjectRequest(sourceBucketName, objectKey, objData[0]);
+ const testPutDestObjectRequest = versioningTestUtils.createPutObjectRequest(destBucketName, objectKey, objData[1]);
before(done => {
cleanup();
- sinon.stub(metadata, 'putObjectMD')
- .callsFake(originalputObjectMD);
-
- async.series([
- callback => bucketPut(authInfo, putDestBucketRequest, log,
- callback),
- callback => bucketPut(authInfo, putSourceBucketRequest, log,
- callback),
- // put source object in source bucket
- callback => objectPut(authInfo, testPutObjectRequest,
- undefined, log, callback),
- ], err => {
- if (err) {
- return done(err);
- }
- versioningTestUtils.assertDataStoreValues(ds, objData.slice(0, 1));
- return done();
- });
+ sinon.stub(metadata, 'putObjectMD').callsFake(originalputObjectMD);
+
+ async.series(
+ [
+ callback => bucketPut(authInfo, putDestBucketRequest, log, callback),
+ callback => bucketPut(authInfo, putSourceBucketRequest, log, callback),
+ // put source object in source bucket
+ callback => objectPut(authInfo, testPutObjectRequest, undefined, log, callback),
+ ],
+ err => {
+ if (err) {
+ return done(err);
+ }
+ versioningTestUtils.assertDataStoreValues(ds, objData.slice(0, 1));
+ return done();
+ },
+ );
});
after(() => {
@@ -271,91 +251,132 @@ describe('non-versioned objectCopy', () => {
const testObjectCopyRequest = _createObjectCopyRequest(destBucketName);
it('should not leave orphans in data when overwriting a multipart upload', done => {
- mpuUtils.createMPU(namespace, destBucketName, objectKey, log,
- (err, testUploadId) => {
+ mpuUtils.createMPU(namespace, destBucketName, objectKey, log, (err, testUploadId) => {
assert.ifError(err);
- objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey,
- undefined, log, err => {
- assert.ifError(err);
- sinon.assert.calledWith(metadata.putObjectMD,
- any, any, any, sinon.match({ oldReplayId: testUploadId }), any, any);
- done();
- });
+ objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, err => {
+ assert.ifError(err);
+ sinon.assert.calledWith(
+ metadata.putObjectMD,
+ any,
+ any,
+ any,
+ sinon.match({ oldReplayId: testUploadId }),
+ any,
+ any,
+ );
+ done();
+ });
});
});
it('should not pass needOplogUpdate when creating object', done => {
- async.series([
- next => objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey,
- undefined, log, next),
- async () => {
- sinon.assert.calledWith(metadata.putObjectMD.lastCall,
- destBucketName, objectKey, sinon.match({
- _data: { originOp: 's3:ObjectCreated:Copy' },
- }), sinon.match({
- needOplogUpdate: undefined,
- originOp: undefined,
- }), any, any);
- },
- ], done);
+ async.series(
+ [
+ next => objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, next),
+ async () => {
+ sinon.assert.calledWith(
+ metadata.putObjectMD.lastCall,
+ destBucketName,
+ objectKey,
+ sinon.match({
+ _data: { originOp: 's3:ObjectCreated:Copy' },
+ }),
+ sinon.match({
+ needOplogUpdate: undefined,
+ originOp: undefined,
+ }),
+ any,
+ any,
+ );
+ },
+ ],
+ done,
+ );
});
it('should not pass needOplogUpdate when replacing object', done => {
- async.series([
- next => objectPut(authInfo, testPutDestObjectRequest, undefined, log, next),
- next => objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey,
- undefined, log, next),
- async () => {
- sinon.assert.calledWith(metadata.putObjectMD.lastCall,
- destBucketName, objectKey, sinon.match({
- _data: { originOp: 's3:ObjectCreated:Copy' },
- }), sinon.match({
- needOplogUpdate: undefined,
- originOp: undefined,
- }), any, any);
- },
- ], done);
+ async.series(
+ [
+ next => objectPut(authInfo, testPutDestObjectRequest, undefined, log, next),
+ next => objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, next),
+ async () => {
+ sinon.assert.calledWith(
+ metadata.putObjectMD.lastCall,
+ destBucketName,
+ objectKey,
+ sinon.match({
+ _data: { originOp: 's3:ObjectCreated:Copy' },
+ }),
+ sinon.match({
+ needOplogUpdate: undefined,
+ originOp: undefined,
+ }),
+ any,
+ any,
+ );
+ },
+ ],
+ done,
+ );
});
it('should pass needOplogUpdate to metadata when replacing archived object', done => {
const archived = {
- archiveInfo: { foo: 0, bar: 'stuff' }
+ archiveInfo: { foo: 0, bar: 'stuff' },
};
- async.series([
- next => objectPut(authInfo, testPutDestObjectRequest, undefined, log, next),
- next => fakeMetadataArchive(destBucketName, objectKey, undefined, archived, next),
- next => objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey,
- undefined, log, next),
- async () => {
- sinon.assert.calledWith(metadata.putObjectMD.lastCall,
- destBucketName, objectKey, any, sinon.match({
- needOplogUpdate: true,
- originOp: 's3:ReplaceArchivedObject',
- }), any, any);
- },
- ], done);
+ async.series(
+ [
+ next => objectPut(authInfo, testPutDestObjectRequest, undefined, log, next),
+ next => fakeMetadataArchive(destBucketName, objectKey, undefined, archived, next),
+ next => objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, next),
+ async () => {
+ sinon.assert.calledWith(
+ metadata.putObjectMD.lastCall,
+ destBucketName,
+ objectKey,
+ any,
+ sinon.match({
+ needOplogUpdate: true,
+ originOp: 's3:ReplaceArchivedObject',
+ }),
+ any,
+ any,
+ );
+ },
+ ],
+ done,
+ );
});
it('should pass needOplogUpdate to metadata when replacing archived object in version suspended bucket', done => {
const archived = {
- archiveInfo: { foo: 0, bar: 'stuff' }
+ archiveInfo: { foo: 0, bar: 'stuff' },
};
- async.series([
- next => bucketPutVersioning(authInfo, suspendVersioningRequest, log, next),
- next => objectPut(authInfo, testPutDestObjectRequest, undefined, log, next),
- next => fakeMetadataArchive(destBucketName, objectKey, undefined, archived, next),
- next => objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey,
- undefined, log, next),
- async () => {
- sinon.assert.calledWith(metadata.putObjectMD.lastCall,
- destBucketName, objectKey, any, sinon.match({
- needOplogUpdate: true,
- originOp: 's3:ReplaceArchivedObject',
- }), any, any);
- },
- ], done);
+ async.series(
+ [
+ next => bucketPutVersioning(authInfo, suspendVersioningRequest, log, next),
+ next => objectPut(authInfo, testPutDestObjectRequest, undefined, log, next),
+ next => fakeMetadataArchive(destBucketName, objectKey, undefined, archived, next),
+ next => objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, next),
+ async () => {
+ sinon.assert.calledWith(
+ metadata.putObjectMD.lastCall,
+ destBucketName,
+ objectKey,
+ any,
+ sinon.match({
+ needOplogUpdate: true,
+ originOp: 's3:ReplaceArchivedObject',
+ }),
+ any,
+ any,
+ );
+ },
+ ],
+ done,
+ );
});
it('should fail to copy object when setting a crr location as the locationConstraint', done => {
@@ -364,14 +385,16 @@ describe('non-versioned objectCopy', () => {
[objectLocationConstraintHeader]: LOCATION_NAME_CRR,
});
- async.series([
- next => objectPut(authInfo, testPutDestObjectRequest, undefined, log, next),
- next => objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey,
- undefined, log, next),
- ], err => {
- assert(err.is.InvalidArgument);
- done();
- });
+ async.series(
+ [
+ next => objectPut(authInfo, testPutDestObjectRequest, undefined, log, next),
+ next => objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, next),
+ ],
+ err => {
+ assert(err.is.InvalidArgument);
+ done();
+ },
+ );
});
});
@@ -379,10 +402,13 @@ describe('objectCopy overheadField', () => {
beforeEach(done => {
cleanup();
sinon.stub(metadata, 'putObjectMD').callsFake(originalputObjectMD);
- async.series([
- next => bucketPut(authInfo, putSourceBucketRequest, log, next),
- next => bucketPut(authInfo, putDestBucketRequest, log, next),
- ], done);
+ async.series(
+ [
+ next => bucketPut(authInfo, putSourceBucketRequest, log, next),
+ next => bucketPut(authInfo, putDestBucketRequest, log, next),
+ ],
+ done,
+ );
});
afterEach(() => {
@@ -391,62 +417,82 @@ describe('objectCopy overheadField', () => {
});
it('should pass overheadField to metadata.putObjectMD for a non-versioned request', done => {
- const testPutObjectRequest =
- versioningTestUtils.createPutObjectRequest(sourceBucketName, objectKey, objData[0]);
+ const testPutObjectRequest = versioningTestUtils.createPutObjectRequest(
+ sourceBucketName,
+ objectKey,
+ objData[0],
+ );
const testObjectCopyRequest = _createObjectCopyRequest(destBucketName);
objectPut(authInfo, testPutObjectRequest, undefined, log, err => {
assert.ifError(err);
- objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log,
- err => {
- assert.ifError(err);
- sinon.assert.calledWith(metadata.putObjectMD.lastCall,
- destBucketName, objectKey, any, sinon.match({ overheadField: sinon.match.array }), any, any);
- done();
- }
- );
+ objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, err => {
+ assert.ifError(err);
+ sinon.assert.calledWith(
+ metadata.putObjectMD.lastCall,
+ destBucketName,
+ objectKey,
+ any,
+ sinon.match({ overheadField: sinon.match.array }),
+ any,
+ any,
+ );
+ done();
+ });
});
});
it('should pass overheadField to metadata.putObjectMD for a versioned request', done => {
- const testPutObjectRequest =
- versioningTestUtils.createPutObjectRequest(sourceBucketName, objectKey, objData[0]);
+ const testPutObjectRequest = versioningTestUtils.createPutObjectRequest(
+ sourceBucketName,
+ objectKey,
+ objData[0],
+ );
const testObjectCopyRequest = _createObjectCopyRequest(destBucketName);
objectPut(authInfo, testPutObjectRequest, undefined, log, err => {
assert.ifError(err);
bucketPutVersioning(authInfo, enableVersioningRequest, log, err => {
assert.ifError(err);
- objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log,
- err => {
- assert.ifError(err);
- sinon.assert.calledWith(metadata.putObjectMD.lastCall,
- destBucketName, objectKey, any,
- sinon.match({ overheadField: sinon.match.array }), any, any
- );
- done();
- }
- );
+ objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, err => {
+ assert.ifError(err);
+ sinon.assert.calledWith(
+ metadata.putObjectMD.lastCall,
+ destBucketName,
+ objectKey,
+ any,
+ sinon.match({ overheadField: sinon.match.array }),
+ any,
+ any,
+ );
+ done();
+ });
});
});
});
it('should pass overheadField to metadata.putObjectMD for a version-suspended request', done => {
- const testPutObjectRequest =
- versioningTestUtils.createPutObjectRequest(sourceBucketName, objectKey, objData[0]);
+ const testPutObjectRequest = versioningTestUtils.createPutObjectRequest(
+ sourceBucketName,
+ objectKey,
+ objData[0],
+ );
const testObjectCopyRequest = _createObjectCopyRequest(destBucketName);
objectPut(authInfo, testPutObjectRequest, undefined, log, err => {
assert.ifError(err);
bucketPutVersioning(authInfo, suspendVersioningRequest, log, err => {
assert.ifError(err);
- objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log,
- err => {
- assert.ifError(err);
- sinon.assert.calledWith(metadata.putObjectMD.lastCall,
- destBucketName, objectKey, any,
- sinon.match({ overheadField: sinon.match.array }), any, any
- );
- done();
- }
- );
+ objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, err => {
+ assert.ifError(err);
+ sinon.assert.calledWith(
+ metadata.putObjectMD.lastCall,
+ destBucketName,
+ objectKey,
+ any,
+ sinon.match({ overheadField: sinon.match.array }),
+ any,
+ any,
+ );
+ done();
+ });
});
});
});
@@ -461,10 +507,16 @@ describe('objectCopy in ingestion bucket', () => {
before(() => {
// Setup multi-backend, this is required for ingestion
- data.switch(new storage.data.MultipleBackendGateway({
- 'us-east-1': dataClient,
- 'us-east-2': dataClient,
- }, metadata, data.locStorageCheckFn));
+ data.switch(
+ new storage.data.MultipleBackendGateway(
+ {
+ 'us-east-1': dataClient,
+ 'us-east-2': dataClient,
+ },
+ metadata,
+ data.locStorageCheckFn,
+ ),
+ );
data.implName = 'multipleBackends';
// "mock" the data location, simulating a backend supporting server-side copy
@@ -497,19 +549,20 @@ describe('objectCopy in ingestion bucket', () => {
sinon.restore();
});
- const newPutIngestBucketRequest = location => new DummyRequest({
- bucketName: destBucketName,
- namespace,
- headers: { host: `${destBucketName}.s3.amazonaws.com` },
- url: '/',
- post: '' +
- '' +
- `${location}` +
- '',
- });
- const putSourceObjectRequest = versioningTestUtils.createPutObjectRequest(
- sourceBucketName, objectKey, objData[0]);
+ const newPutIngestBucketRequest = location =>
+ new DummyRequest({
+ bucketName: destBucketName,
+ namespace,
+ headers: { host: `${destBucketName}.s3.amazonaws.com` },
+ url: '/',
+ post:
+ '' +
+ '' +
+ `${location}` +
+ '',
+ });
+ const putSourceObjectRequest = versioningTestUtils.createPutObjectRequest(sourceBucketName, objectKey, objData[0]);
const newPutObjectRequest = params => {
const { location } = params || {};
const r = _createObjectCopyRequest(destBucketName);
@@ -526,17 +579,28 @@ describe('objectCopy in ingestion bucket', () => {
const versionID = versioning.VersionID.encode(versioning.VersionID.generateVersionId('0', ''));
dataClient.copyObject = sinon.stub().yields(null, objectKey, versionID);
- async.series([
- next => bucketPut(authInfo, putSourceBucketRequest, log, next),
- next => bucketPut(authInfo, newPutIngestBucketRequest('us-east-1:ingest'), log, next),
- next => objectPut(authInfo, putSourceObjectRequest, undefined, log, next),
- next => objectCopy(authInfo, newPutObjectRequest(), sourceBucketName, objectKey, undefined, log,
- (err, xml, headers) => {
- assert.ifError(err);
- assert.strictEqual(headers['x-amz-version-id'], versionID);
- next();
- }),
- ], done);
+ async.series(
+ [
+ next => bucketPut(authInfo, putSourceBucketRequest, log, next),
+ next => bucketPut(authInfo, newPutIngestBucketRequest('us-east-1:ingest'), log, next),
+ next => objectPut(authInfo, putSourceObjectRequest, undefined, log, next),
+ next =>
+ objectCopy(
+ authInfo,
+ newPutObjectRequest(),
+ sourceBucketName,
+ objectKey,
+ undefined,
+ log,
+ (err, xml, headers) => {
+ assert.ifError(err);
+ assert.strictEqual(headers['x-amz-version-id'], versionID);
+ next();
+ },
+ ),
+ ],
+ done,
+ );
});
it('should not use the versionID from the backend when writing in another location', done => {
@@ -544,34 +608,56 @@ describe('objectCopy in ingestion bucket', () => {
dataClient.copyObject = sinon.stub().yields(null, objectKey, versionID);
const copyObjectRequest = newPutObjectRequest({ location: 'us-east-2' });
- async.series([
- next => bucketPut(authInfo, putSourceBucketRequest, log, next),
- next => bucketPut(authInfo, newPutIngestBucketRequest('us-east-1:ingest'), log, next),
- next => objectPut(authInfo, putSourceObjectRequest, undefined, log, next),
- next => objectCopy(authInfo, copyObjectRequest, sourceBucketName, objectKey, undefined, log,
- (err, xml, headers) => {
- assert.ifError(err);
- assert.notEqual(headers['x-amz-version-id'], versionID);
- next();
- }),
- ], done);
+ async.series(
+ [
+ next => bucketPut(authInfo, putSourceBucketRequest, log, next),
+ next => bucketPut(authInfo, newPutIngestBucketRequest('us-east-1:ingest'), log, next),
+ next => objectPut(authInfo, putSourceObjectRequest, undefined, log, next),
+ next =>
+ objectCopy(
+ authInfo,
+ copyObjectRequest,
+ sourceBucketName,
+ objectKey,
+ undefined,
+ log,
+ (err, xml, headers) => {
+ assert.ifError(err);
+ assert.notEqual(headers['x-amz-version-id'], versionID);
+ next();
+ },
+ ),
+ ],
+ done,
+ );
});
it('should not use the versionID from the backend when it is not a valid versionID', done => {
const versionID = undefined;
dataClient.copyObject = sinon.stub().yields(null, objectKey, versionID);
- async.series([
- next => bucketPut(authInfo, putSourceBucketRequest, log, next),
- next => bucketPut(authInfo, newPutIngestBucketRequest('us-east-1:ingest'), log, next),
- next => objectPut(authInfo, putSourceObjectRequest, undefined, log, next),
- next => objectCopy(authInfo, newPutObjectRequest(), sourceBucketName, objectKey, undefined, log,
- (err, xml, headers) => {
- assert.ifError(err);
- assert.notEqual(headers['x-amz-version-id'], versionID);
- next();
- }),
- ], done);
+ async.series(
+ [
+ next => bucketPut(authInfo, putSourceBucketRequest, log, next),
+ next => bucketPut(authInfo, newPutIngestBucketRequest('us-east-1:ingest'), log, next),
+ next => objectPut(authInfo, putSourceObjectRequest, undefined, log, next),
+ next =>
+ objectCopy(
+ authInfo,
+ newPutObjectRequest(),
+ sourceBucketName,
+ objectKey,
+ undefined,
+ log,
+ (err, xml, headers) => {
+ assert.ifError(err);
+ assert.notEqual(headers['x-amz-version-id'], versionID);
+ next();
+ },
+ ),
+ ],
+ done,
+ );
});
});
@@ -580,12 +666,21 @@ describe('objectCopy with objectKeyByteLimit', () => {
beforeEach(done => {
cleanup();
- async.series([
- next => bucketPut(authInfo, putDestBucketRequest, log, next),
- next => bucketPut(authInfo, putSourceBucketRequest, log, next),
- next => objectPut(authInfo, versioningTestUtils.createPutObjectRequest(
- sourceBucketName, objectKey, objData[0]), undefined, log, next),
- ], done);
+ async.series(
+ [
+ next => bucketPut(authInfo, putDestBucketRequest, log, next),
+ next => bucketPut(authInfo, putSourceBucketRequest, log, next),
+ next =>
+ objectPut(
+ authInfo,
+ versioningTestUtils.createPutObjectRequest(sourceBucketName, objectKey, objData[0]),
+ undefined,
+ log,
+ next,
+ ),
+ ],
+ done,
+ );
});
afterEach(() => {
@@ -598,13 +693,12 @@ describe('objectCopy with objectKeyByteLimit', () => {
testCopyObjectRequest.objectKey = longDestKey;
testCopyObjectRequest.url = `/${destBucketName}/${longDestKey}`;
- objectCopy(authInfo, testCopyObjectRequest, sourceBucketName, objectKey,
- undefined, log, err => {
- assert(err);
- assert.strictEqual(err.KeyTooLong, true);
- assert.match(err.description, /915/);
- done();
- });
+ objectCopy(authInfo, testCopyObjectRequest, sourceBucketName, objectKey, undefined, log, err => {
+ assert(err);
+ assert.strictEqual(err.KeyTooLong, true);
+ assert.match(err.description, /915/);
+ done();
+ });
});
it('should accept destination object key longer than 915 bytes with objectKeyByteLimit', done => {
@@ -615,12 +709,11 @@ describe('objectCopy with objectKeyByteLimit', () => {
testCopyObjectRequest.objectKey = longDestKey;
testCopyObjectRequest.url = `/${destBucketName}/${longDestKey}`;
- objectCopy(authInfo, testCopyObjectRequest, sourceBucketName, objectKey,
- undefined, log, (err, xml) => {
- assert.ifError(err);
- assert(xml);
- done();
- });
+ objectCopy(authInfo, testCopyObjectRequest, sourceBucketName, objectKey, undefined, log, (err, xml) => {
+ assert.ifError(err);
+ assert(xml);
+ done();
+ });
});
it('should reject destination object key exceeding objectKeyByteLimit', done => {
@@ -631,12 +724,69 @@ describe('objectCopy with objectKeyByteLimit', () => {
testCopyObjectRequest.objectKey = longDestKey;
testCopyObjectRequest.url = `/${destBucketName}/${longDestKey}`;
- objectCopy(authInfo, testCopyObjectRequest, sourceBucketName, objectKey,
- undefined, log, err => {
- assert(err);
- assert.strictEqual(err.KeyTooLong, true);
- assert.match(err.description, /1024/);
- done();
- });
+ objectCopy(authInfo, testCopyObjectRequest, sourceBucketName, objectKey, undefined, log, err => {
+ assert(err);
+ assert.strictEqual(err.KeyTooLong, true);
+ assert.match(err.description, /1024/);
+ done();
+ });
+ });
+});
+
+describe('objectCopy source size limit', () => {
+ const testPutObjectRequest = versioningTestUtils.createPutObjectRequest(sourceBucketName, objectKey, objData[0]);
+ const sourceSize = objData[0].length;
+ let originalMaximumUploadSize;
+
+ before(done => {
+ cleanup();
+ originalMaximumUploadSize = constants.maximumAllowedUploadSize;
+ async.series(
+ [
+ callback => bucketPut(authInfo, putDestBucketRequest, log, callback),
+ callback => bucketPut(authInfo, putSourceBucketRequest, log, callback),
+ callback => objectPut(authInfo, testPutObjectRequest, undefined, log, callback),
+ ],
+ done,
+ );
+ });
+
+ after(() => {
+ constants.maximumAllowedUploadSize = originalMaximumUploadSize;
+ config.bypassMaxPutObjectSize = false;
+ cleanup();
+ });
+
+ it('should allow CopyObject when source size equals the limit', done => {
+ constants.maximumAllowedUploadSize = sourceSize;
+ const testObjectCopyRequest = _createObjectCopyRequest(destBucketName);
+ objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, err => {
+ assert.ifError(err);
+ done();
+ });
+ });
+
+ it('should reject CopyObject when source size exceeds the limit', done => {
+ constants.maximumAllowedUploadSize = sourceSize - 1;
+ const testObjectCopyRequest = _createObjectCopyRequest(destBucketName);
+ objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, err => {
+ assert(err);
+ assert.strictEqual(err.is.InvalidRequest, true);
+ assert.match(
+ err.description,
+ /The specified copy source is larger than the maximum allowable size for a copy source/,
+ );
+ done();
+ });
+ });
+
+ it('should allow CopyObject when source size exceeds the limit but bypass flag is set', done => {
+ constants.maximumAllowedUploadSize = sourceSize - 1;
+ config.bypassMaxPutObjectSize = true;
+ const testObjectCopyRequest = _createObjectCopyRequest(destBucketName);
+ objectCopy(authInfo, testObjectCopyRequest, sourceBucketName, objectKey, undefined, log, err => {
+ assert.ifError(err);
+ done();
+ });
});
});