Skip to content

Commit d9b4211

Browse files
committed
!fixup Support the new API GetObjectAttributes
1 parent 023e610 commit d9b4211

6 files changed

Lines changed: 1146 additions & 742 deletions

File tree

lib/api/apiUtils/object/parseAttributesHeader.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
const { errorInstances } = require('arsenal');
22
const { allowedObjectAttributes } = require('../../../../constants');
33

4+
/**
5+
* parseAttributesHeaders - Parse and validate the x-amz-object-attributes header
6+
* @param {object} headers - request headers
7+
* @returns {string[]} - array of valid attribute names
8+
* @throws {Error} - InvalidRequest if header is missing/empty, InvalidArgument if attribute is invalid
9+
*/
410
function parseAttributesHeaders(headers) {
511
const raw = headers['x-amz-object-attributes'] || '';
612

@@ -10,14 +16,14 @@ function parseAttributesHeaders(headers) {
1016
.filter(s => s !== '');
1117

1218
if (attributes.length === 0) {
13-
return errorInstances.InvalidRequest.customizeDescription(
19+
throw errorInstances.InvalidRequest.customizeDescription(
1420
'The x-amz-object-attributes header specifying the attributes to be retrieved is either missing or empty',
1521
);
1622
}
1723

1824
const invalids = attributes.filter(s => !allowedObjectAttributes.has(s));
1925
if (invalids.length > 0) {
20-
return errorInstances.InvalidArgument.customizeDescription('Invalid attribute name specified.');
26+
throw errorInstances.InvalidArgument.customizeDescription('Invalid attribute name specified.');
2127
}
2228

2329
return attributes;

lib/api/objectGetAttributes.js

Lines changed: 77 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { waterfall } = require('async');
1+
const { promisify } = require('util');
22
const xml2js = require('xml2js');
33
const { errors } = require('arsenal');
44
const { standardMetadataValidateBucketAndObj } = require('../metadata/metadataUtils');
@@ -7,9 +7,31 @@ const parseAttributesHeaders = require('./apiUtils/object/parseAttributesHeader'
77
const { decodeVersionId, getVersionIdResHeader } = require('./apiUtils/object/versioning');
88
const { checkExpectedBucketOwner } = require('./apiUtils/authorization/bucketOwner');
99
const { pushMetric } = require('../utapi/utilities');
10+
const { getPartCountFromMd5 } = require('./apiUtils/object/partInfo');
1011

1112
const OBJECT_GET_ATTRIBUTES = 'objectGetAttributes';
1213

14+
const checkExpectedBucketOwnerPromise = promisify(checkExpectedBucketOwner);
15+
16+
/**
17+
* validateBucketAndObjPromise - Promisified wrapper for standardMetadataValidateBucketAndObj
18+
* @param {object} params - validation parameters
19+
* @param {boolean} actionImplicitDenies - whether action has implicit denies
20+
* @param {object} log - Werelogs logger
21+
* @returns {Promise<{bucket: BucketInfo, objMD: object}>} - bucket and object metadata
22+
* @throws {Error} - rejects with error from standardMetadataValidateBucketAndObj
23+
*/
24+
function validateBucketAndObjPromise(params, actionImplicitDenies, log) {
25+
return new Promise((resolve, reject) => {
26+
standardMetadataValidateBucketAndObj(params, actionImplicitDenies, log, (err, bucket, objMD) => {
27+
if (err) {
28+
return reject(err);
29+
}
30+
return resolve({ bucket, objMD });
31+
});
32+
});
33+
}
34+
1335
/**
1436
* buildXmlResponse - Build XML response for GetObjectAttributes
1537
* @param {object} objMD - object metadata
@@ -23,12 +45,16 @@ function buildXmlResponse(objMD, attributes) {
2345
attrResp.ETag = objMD['content-md5'];
2446
}
2547

48+
// NOTE: Checksum is not implemented
2649
if (attributes.includes('Checksum')) {
2750
attrResp.Checksum = {};
2851
}
2952

3053
if (attributes.includes('ObjectParts')) {
31-
attrResp.ObjectParts = {};
54+
const partCount = getPartCountFromMd5(objMD);
55+
if (partCount) {
56+
attrResp.ObjectParts = { PartsCount: partCount };
57+
}
3258
}
3359

3460
if (attributes.includes('StorageClass')) {
@@ -51,19 +77,17 @@ function buildXmlResponse(objMD, attributes) {
5177
* @param {function} callback - callback to server
5278
* @return {undefined}
5379
*/
54-
function objectGetAttributes(authInfo, request, log, callback) {
55-
log.debug('processing request', { method: OBJECT_GET_ATTRIBUTES });
80+
async function objectGetAttributes(authInfo, request, log, callback) {
81+
log.trace('processing request', { method: OBJECT_GET_ATTRIBUTES });
5682
const { bucketName, objectKey, headers, actionImplicitDenies } = request;
5783

58-
const decodedVersionId = decodeVersionId(request.query);
59-
if (decodedVersionId instanceof Error) {
60-
log.trace('invalid versionId query', {
61-
versionId: request.query.versionId,
62-
error: decodedVersionId,
63-
});
64-
return callback(decodedVersionId);
84+
let responseHeaders = {};
85+
86+
const versionId = decodeVersionId(request.query);
87+
if (versionId instanceof Error) {
88+
log.debug('invalid versionId query', { versionId: request.query.versionId, error: versionId });
89+
throw versionId;
6590
}
66-
const versionId = decodedVersionId;
6791

6892
const metadataValParams = {
6993
authInfo,
@@ -75,72 +99,48 @@ function objectGetAttributes(authInfo, request, log, callback) {
7599
request,
76100
};
77101

78-
let bucket = null;
79-
let objMD = null;
80-
let responseHeaders = null;
81-
let xml = null;
82-
83-
return waterfall(
84-
[
85-
next =>
86-
standardMetadataValidateBucketAndObj(metadataValParams, actionImplicitDenies, log, (err, b, o) => {
87-
bucket = b;
88-
objMD = o;
89-
return next(err);
90-
}),
91-
next => checkExpectedBucketOwner(headers, bucket, log, next),
92-
next => {
93-
responseHeaders = collectCorsHeaders(headers.origin, request.method, bucket);
94-
if (objMD) {
95-
responseHeaders['x-amz-version-id'] = getVersionIdResHeader(bucket.getVersioningConfiguration(), objMD);
96-
responseHeaders['Last-Modified'] = objMD['last-modified'] && new Date(objMD['last-modified']).toUTCString();
97-
}
98-
99-
return next();
100-
},
101-
next => {
102-
if (!objMD) {
103-
const e = versionId ? errors.NoSuchVersion : errors.NoSuchKey;
104-
return next(e);
105-
}
106-
107-
if (objMD.isDeleteMarker) {
108-
responseHeaders['x-amz-delete-marker'] = true;
109-
return next(errors.MethodNotAllowed);
110-
}
111-
112-
const attributes = parseAttributesHeaders(headers);
113-
if (attributes instanceof Error) {
114-
log.trace('invalid x-amz-object-attributes header', {
115-
headers,
116-
error: attributes,
117-
});
118-
return next(attributes);
119-
}
120-
121-
xml = buildXmlResponse(objMD, attributes);
122-
return next();
123-
},
124-
],
125-
err => {
126-
if (err) {
127-
log.debug('error processing request', {
128-
error: err,
129-
method: OBJECT_GET_ATTRIBUTES,
130-
});
131-
return callback(err, '', responseHeaders);
132-
}
102+
try {
103+
const { bucket, objMD } = await validateBucketAndObjPromise(metadataValParams, actionImplicitDenies, log);
104+
await checkExpectedBucketOwnerPromise(headers, bucket, log);
105+
106+
responseHeaders = collectCorsHeaders(headers.origin, request.method, bucket);
107+
if (objMD) {
108+
responseHeaders['x-amz-version-id'] = getVersionIdResHeader(bucket.getVersioningConfiguration(), objMD);
109+
responseHeaders['Last-Modified'] = objMD['last-modified'] && new Date(objMD['last-modified']).toUTCString();
110+
}
111+
112+
if (!objMD) {
113+
const err = versionId ? errors.NoSuchVersion : errors.NoSuchKey;
114+
log.debug('object not found', { bucket: bucketName, key: objectKey, versionId });
115+
throw err;
116+
}
117+
118+
if (objMD.isDeleteMarker) {
119+
log.debug('attempt to get attributes of a delete marker', { bucket: bucketName, key: objectKey, versionId });
120+
responseHeaders['x-amz-delete-marker'] = true;
121+
throw errors.MethodNotAllowed;
122+
}
123+
124+
const attributes = parseAttributesHeaders(headers);
125+
126+
pushMetric(OBJECT_GET_ATTRIBUTES, log, {
127+
authInfo,
128+
bucket: bucketName,
129+
keys: [objectKey],
130+
versionId: objMD?.versionId,
131+
location: objMD?.dataStoreName,
132+
});
133+
134+
const xml = buildXmlResponse(objMD, attributes);
135+
return callback(null, xml, responseHeaders);
136+
} catch (err) {
137+
log.debug('error processing request', {
138+
error: err,
139+
method: OBJECT_GET_ATTRIBUTES,
140+
});
133141

134-
pushMetric(OBJECT_GET_ATTRIBUTES, log, {
135-
authInfo,
136-
bucket: bucketName,
137-
keys: [objectKey],
138-
versionId: objMD?.versionId,
139-
location: objMD?.dataStoreName,
140-
});
141-
return callback(err, xml, responseHeaders);
142-
},
143-
);
142+
return callback(err, null, responseHeaders);
143+
}
144144
}
145145

146146
module.exports = objectGetAttributes;

0 commit comments

Comments
 (0)