Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 121 additions & 126 deletions lib/api/bucketGet.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,9 @@ const escapeForXml = s3middleware.escapeForXml;
const { pushMetric } = require('../utapi/utilities');
const versionIdUtils = versioning.VersionID;
const monitoring = require('../utilities/monitoringHandler');
const { generateToken, decryptToken }
= require('../api/apiUtils/object/continueToken');
const { generateToken, decryptToken } = require('../api/apiUtils/object/continueToken');

// do not url encode the continuation tokens
const skipUrlEncoding = new Set([
'ContinuationToken',
'NextContinuationToken',
]);
const xmlParamsToSkipUrlEncoding = new Set(['ContinuationToken', 'NextContinuationToken',]);

/* Sample XML response for GET bucket objects V2:
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
Expand Down Expand Up @@ -122,17 +117,16 @@ function processVersions(bucketName, listParams, list) {
{ tag: 'IsTruncated', value: isTruncated },
];

const escapeXmlFn = listParams.encoding === 'url' ?
querystring.escape : escapeForXml;
const escapeXmlFn = listParams.encoding === 'url' ? querystring.escape : escapeForXml;
xmlParams.forEach(p => {
if (p.value) {
const val = p.tag !== 'NextVersionIdMarker' || p.value === 'null' ?
p.value : versionIdUtils.encode(p.value);
p.value :
versionIdUtils.encode(p.value);
xml.push(`<${p.tag}>${escapeXmlFn(val)}</${p.tag}>`);
}
});
let lastKey = listParams.keyMarker ?
escapeXmlFn(listParams.keyMarker) : undefined;
let lastKey = listParams.keyMarker ? escapeXmlFn(listParams.keyMarker) : undefined;
list.Versions.forEach(item => {
const v = item.value;
const objectKey = escapeXmlFn(item.key);
Expand All @@ -143,7 +137,8 @@ function processVersions(bucketName, listParams, list) {
`<Key>${objectKey}</Key>`,
'<VersionId>',
(v.IsNull || v.VersionId === undefined) ?
'null' : versionIdUtils.encode(v.VersionId),
'null'
: versionIdUtils.encode(v.VersionId),
'</VersionId>',
`<IsLatest>${isLatest}</IsLatest>`,
`<LastModified>${v.LastModified}</LastModified>`,
Expand Down Expand Up @@ -182,31 +177,19 @@ function processMasterVersions(bucketName, listParams, list) {
];

if (listParams.v2) {
xmlParams.push(
{ tag: 'StartAfter', value: listParams.startAfter || '' });
xmlParams.push(
{ tag: 'FetchOwner', value: `${listParams.fetchOwner}` });
xmlParams.push({
tag: 'ContinuationToken',
value: generateToken(listParams.continuationToken) || '',
});
xmlParams.push({
tag: 'NextContinuationToken',
value: generateToken(list.NextContinuationToken),
});
xmlParams.push({
tag: 'KeyCount',
value: list.Contents ? list.Contents.length : 0,
});
xmlParams.push({ tag: 'StartAfter', value: listParams.startAfter || '' });
xmlParams.push({ tag: 'FetchOwner', value: `${listParams.fetchOwner}` });
xmlParams.push({ tag: 'ContinuationToken', value: generateToken(listParams.continuationToken) || '', });
xmlParams.push({ tag: 'NextContinuationToken', value: generateToken(list.NextContinuationToken), });
xmlParams.push({ tag: 'KeyCount', value: list.Contents ? list.Contents.length : 0, });
} else {
xmlParams.push({ tag: 'Marker', value: listParams.marker || '' });
xmlParams.push({ tag: 'NextMarker', value: list.NextMarker });
}

const escapeXmlFn = listParams.encoding === 'url' ?
querystring.escape : escapeForXml;
const escapeXmlFn = listParams.encoding === 'url' ? querystring.escape : escapeForXml;
xmlParams.forEach(p => {
if (p.value && skipUrlEncoding.has(p.tag)) {
if (p.value && xmlParamsToSkipUrlEncoding.has(p.tag)) {
xml.push(`<${p.tag}>${p.value}</${p.tag}>`);
} else if (p.value || p.tag === 'KeyCount' || p.tag === 'MaxKeys') {
xml.push(`<${p.tag}>${escapeXmlFn(p.value)}</${p.tag}>`);
Expand Down Expand Up @@ -246,15 +229,14 @@ function processMasterVersions(bucketName, listParams, list) {
);
});
list.CommonPrefixes.forEach(item => {
const val = escapeXmlFn(item);
xml.push(`<CommonPrefixes><Prefix>${val}</Prefix></CommonPrefixes>`);
xml.push(`<CommonPrefixes><Prefix>${escapeXmlFn(item)}</Prefix></CommonPrefixes>`);
});
xml.push('</ListBucketResult>');

return xml.join('');
}

function handleResult(listParams, requestMaxKeys, encoding, authInfo,
bucketName, list, corsHeaders, log, callback) {
function handleResult(listParams, requestMaxKeys, encoding, authInfo, bucketName, list, log) {
// eslint-disable-next-line no-param-reassign
listParams.maxKeys = requestMaxKeys;
// eslint-disable-next-line no-param-reassign
Expand All @@ -267,9 +249,24 @@ function handleResult(listParams, requestMaxKeys, encoding, authInfo,
}
pushMetric('listBucket', log, { authInfo, bucket: bucketName });
monitoring.promMetrics('GET', bucketName, '200', 'listBucket');
return callback(null, res, corsHeaders);
return res;
}

const validateBucket = (params, denials, log) => new Promise(resolve => {
standardMetadataValidateBucket(params, denials, log, (error, bucket) => {
resolve({ error, bucket });
});
});

const getObjectListing = (bucketName, listParams, log) => new Promise((resolve, reject) => {
services.getObjectListing(bucketName, listParams, log, (err, list) => {
if (err) {
return reject(err);
}
return resolve(list);
});
});

/**
* bucketGet - Return list of objects in bucket, supports v1 & v2
* @param {AuthInfo} authInfo - Instance of AuthInfo class with
Expand All @@ -280,92 +277,86 @@ function handleResult(listParams, requestMaxKeys, encoding, authInfo,
* with either error code or xml response body
* @return {undefined}
*/
function bucketGet(authInfo, request, log, callback) {
const params = request.query;
const bucketName = request.bucketName;
const v2 = params['list-type'];
if (v2 !== undefined && Number.parseInt(v2, 10) !== 2) {
return callback(errorInstances.InvalidArgument.customizeDescription('Invalid ' +
'List Type specified in Request'));
}
if (v2) {
log.addDefaultFields({
action: 'ListObjectsV2',
});
if (request.serverAccessLog) {
// eslint-disable-next-line no-param-reassign
request.serverAccessLog.analyticsAction = 'ListObjectsV2';
async function bucketGet(authInfo, request, log, callback) {
try {
const params = request.query;
const bucketName = request.bucketName;
const v2 = params['list-type'];

if (v2 !== undefined && Number.parseInt(v2, 10) !== 2) {
return callback(errorInstances.InvalidArgument.customizeDescription(
'Invalid List Type specified in Request'
));
}
} else if (params.versions !== undefined) {
log.addDefaultFields({
action: 'ListObjectVersions',
});
if (request.serverAccessLog) {
// eslint-disable-next-line no-param-reassign
request.serverAccessLog.analyticsAction = 'ListObjectVersions';

if (v2) {
log.addDefaultFields({ action: 'ListObjectsV2', });
if (request.serverAccessLog) {
// eslint-disable-next-line no-param-reassign
request.serverAccessLog.analyticsAction = 'ListObjectsV2';
}
} else if (params.versions !== undefined) {
log.addDefaultFields({ action: 'ListObjectVersions', });
if (request.serverAccessLog) {
// eslint-disable-next-line no-param-reassign
request.serverAccessLog.analyticsAction = 'ListObjectVersions';
}
}
}
log.debug('processing request', { method: 'bucketGet' });
const encoding = params['encoding-type'];
if (encoding !== undefined && encoding !== 'url') {
monitoring.promMetrics(
'GET', bucketName, 400, 'listBucket');
return callback(errorInstances.InvalidArgument.customizeDescription('Invalid ' +
'Encoding Method specified in Request'));
}
const requestMaxKeys = params['max-keys'] ?
Number.parseInt(params['max-keys'], 10) : 1000;
if (Number.isNaN(requestMaxKeys) || requestMaxKeys < 0) {
monitoring.promMetrics(
'GET', bucketName, 400, 'listBucket');
return callback(errors.InvalidArgument);
}
// AWS only returns 1000 keys even if max keys are greater.
// Max keys stated in response xml can be greater than actual
// keys returned.
const actualMaxKeys = Math.min(constants.listingHardLimit, requestMaxKeys);
log.debug('processing request', { method: 'bucketGet' });
const encoding = params['encoding-type'];
if (encoding !== undefined && encoding !== 'url') {
monitoring.promMetrics('GET', bucketName, 400, 'listBucket');
return callback(errorInstances.InvalidArgument.customizeDescription(
'Invalid Encoding Method specified in Request'
));
}
const requestMaxKeys = params['max-keys'] ? Number.parseInt(params['max-keys'], 10) : 1000;
if (Number.isNaN(requestMaxKeys) || requestMaxKeys < 0) {
monitoring.promMetrics('GET', bucketName, 400, 'listBucket');
return callback(errors.InvalidArgument);
}
const actualMaxKeys = Math.min(constants.listingHardLimit, requestMaxKeys);

const metadataValParams = {
authInfo,
bucketName,
requestType: request.apiMethods || 'bucketGet',
request,
};
const listParams = {
listingType: 'DelimiterMaster',
maxKeys: actualMaxKeys,
prefix: params.prefix,
};
const metadataValParams = {
authInfo,
bucketName,
requestType: request.apiMethods || 'bucketGet',
request,
};
const listParams = {
listingType: 'DelimiterMaster',
maxKeys: actualMaxKeys,
prefix: params.prefix,
};

if (params.delimiter) {
listParams.delimiter = params.delimiter;
}
if (params.delimiter) {
listParams.delimiter = params.delimiter;
}

if (v2) {
listParams.v2 = true;
listParams.startAfter = params['start-after'];
listParams.continuationToken =
decryptToken(params['continuation-token']);
listParams.fetchOwner = params['fetch-owner'] === 'true';
} else {
listParams.marker = params.marker;
}
if (v2) {
listParams.v2 = true;
listParams.startAfter = params['start-after'];
listParams.continuationToken = decryptToken(params['continuation-token']);
listParams.fetchOwner = params['fetch-owner'] === 'true';
} else {
listParams.marker = params.marker;
}

standardMetadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => {
const corsHeaders = collectCorsHeaders(request.headers.origin,
request.method, bucket);
if (err) {
log.debug('error processing request', { error: err });
monitoring.promMetrics(
'GET', bucketName, err.code, 'listBucket');
return callback(err, null, corsHeaders);
const { error, bucket } = await validateBucket(metadataValParams, request.actionImplicitDenies, log);
const corsHeaders = collectCorsHeaders(request.headers.origin, request.method, bucket);

if (error) {
log.debug('error processing request', { error });
monitoring.promMetrics('GET', bucketName, error.code, 'listBucket');
return callback(error, null, corsHeaders);
}
if (params.versions !== undefined) {
listParams.listingType = 'DelimiterVersions';
delete listParams.marker;
listParams.keyMarker = params['key-marker'];
listParams.versionIdMarker = params['version-id-marker'] ?
versionIdUtils.decode(params['version-id-marker']) : undefined;
versionIdUtils.decode(params['version-id-marker']) :
undefined;
}
if (!requestMaxKeys) {
const emptyList = {
Expand All @@ -374,26 +365,30 @@ function bucketGet(authInfo, request, log, callback) {
Versions: [],
IsTruncated: false,
};
return handleResult(listParams, requestMaxKeys, encoding, authInfo,
bucketName, emptyList, corsHeaders, log, callback);
const res = handleResult(listParams, requestMaxKeys, encoding, authInfo, bucketName, emptyList, log);
return callback(null, res, corsHeaders);
}
return services.getObjectListing(bucketName, listParams, log,
(err, list) => {
if (err) {
log.debug('error processing request', { error: err });
monitoring.promMetrics(
'GET', bucketName, err.code, 'listBucket');
return callback(err, null, corsHeaders);
}
return handleResult(listParams, requestMaxKeys, encoding, authInfo,
bucketName, list, corsHeaders, log, callback);
});
});
return undefined;

let list;
try {
list = await getObjectListing(bucketName, listParams, log);
} catch (err) {
log.debug('error processing request', { error: err });
monitoring.promMetrics('GET', bucketName, err.code, 'listBucket');
return callback(err, null, corsHeaders);
}

const res = handleResult(listParams, requestMaxKeys, encoding, authInfo, bucketName, list, log);
return callback(null, res, corsHeaders);
} catch (err) {
log.error('unhandled error in bucketGet', { error: err });
return callback(errors.InternalError);
}
}

module.exports = {
processVersions,
processMasterVersions,
bucketGet,
};

Loading
Loading