Skip to content
140 changes: 105 additions & 35 deletions lib/api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const objectGetRetention = require('./objectGetRetention');
const objectGetTagging = require('./objectGetTagging');
const objectHead = require('./objectHead');
const objectPut = require('./objectPut');
const objectPost = require('./objectPost');
const objectPutACL = require('./objectPutACL');
const objectPutLegalHold = require('./objectPutLegalHold');
const objectPutTagging = require('./objectPutTagging');
Expand All @@ -68,6 +69,8 @@ const validateQueryAndHeaders = require('../utilities/validateQueryAndHeaders');
const parseCopySource = require('./apiUtils/object/parseCopySource');
const { tagConditionKeyAuth } = require('./apiUtils/authorization/tagConditionKeys');
const checkHttpHeadersSize = require('./apiUtils/object/checkHttpHeadersSize');
const { decryptToken } = require('./apiUtils/object/continueToken');
const busboy = require('busboy');

const monitoringMap = policies.actionMaps.actionMonitoringMapS3;

Expand Down Expand Up @@ -184,8 +187,98 @@ const api = {
}
return { returnTagCount, isImplicitDeny };
}
let bb;
let fileEventData = null;

if (apiMethod === 'objectPost' && request.headers['content-type'].includes('multipart/form-data')) {
bb = busboy({ headers: request.headers });
}

return async.waterfall([
next => {
if (apiMethod === 'objectPut' || apiMethod === 'objectPutPart') {
return next(null);
}
if (apiMethod === 'objectPost' && request.headers['content-type'].includes('multipart/form-data')) {
writeContinue(request, response);

let algoOK = false;
let credOK = false;
let dateOK = false;
let sigOK = false;
let policyOK = false;
request.formData = {};
bb.on('field', (fieldname, val) => {
request.formData[fieldname] = val;
if (request.formData.Policy) {
request.formData.decryptedPolicy = JSON.parse(decryptToken(request.formData.Policy));
}

// TODO - put content type field for file in request
if (fieldname === 'X-Amz-Algorithm') {
algoOK = true;
}
if (fieldname === 'X-Amz-Credential') {
credOK = true;
}
if (fieldname === 'X-Amz-Date') {
dateOK = true;
}
if (fieldname === 'X-Amz-Signature') {
sigOK = true;
}
if (fieldname === 'Policy') {
policyOK = true;
}
});

bb.on('file', (fieldname, file, filename, encoding, mimetype) => {
fileEventData = { fieldname, file, filename, encoding, mimetype };
if (algoOK && credOK && dateOK && sigOK && policyOK) {
return next(null);
}
});

bb.on('finish', () => {
// if authorization field is not found, return error
if (!algoOK || !credOK || !dateOK || !sigOK || !policyOK) {
return next(errors.InvalidRequest);
}
});
request.pipe(bb);
} else {
// issue 100 Continue to the client
writeContinue(request, response);
const MAX_POST_LENGTH = request.method === 'POST' ?
1024 * 1024 : 1024 * 1024 / 2; // 1 MB or 512 KB
const post = [];
let postLength = 0;
request.on('data', chunk => {
postLength += chunk.length;
// Sanity check on post length
if (postLength <= MAX_POST_LENGTH) {
post.push(chunk);
}
});

request.on('error', err => {
log.trace('error receiving request', {
error: err,
});
return next(errors.InternalError);
});

request.on('end', () => {
if (postLength > MAX_POST_LENGTH) {
log.error('body length is too long for request type',
{ postLength });
return next(errors.InvalidRequest);
}
return next(null);
});
}
return undefined;
},
next => auth.server.doAuth(
request, log, (err, userInfo, authorizationResults, streamingV4Params) => {
if (err) {
Expand All @@ -200,41 +293,7 @@ const api = {
authNames.userName = userInfo.getIAMdisplayName();
}
log.addDefaultFields(authNames);
if (apiMethod === 'objectPut' || apiMethod === 'objectPutPart') {
return next(null, userInfo, authorizationResults, streamingV4Params);
}
// issue 100 Continue to the client
writeContinue(request, response);
const MAX_POST_LENGTH = request.method === 'POST' ?
1024 * 1024 : 1024 * 1024 / 2; // 1 MB or 512 KB
const post = [];
let postLength = 0;
request.on('data', chunk => {
postLength += chunk.length;
// Sanity check on post length
if (postLength <= MAX_POST_LENGTH) {
post.push(chunk);
}
});

request.on('error', err => {
log.trace('error receiving request', {
error: err,
});
return next(errors.InternalError);
});

request.on('end', () => {
if (postLength > MAX_POST_LENGTH) {
log.error('body length is too long for request type',
{ postLength });
return next(errors.InvalidRequest);
}
// Convert array of post buffers into one string
request.post = Buffer.concat(post, postLength).toString();
return next(null, userInfo, authorizationResults, streamingV4Params);
});
return undefined;
return next(null, userInfo, authorizationResults, streamingV4Params);
},
// Tag condition keys require information from CloudServer for evaluation
(userInfo, authorizationResults, streamingV4Params, next) => tagConditionKeyAuth(
Expand All @@ -244,6 +303,10 @@ const api = {
apiMethod,
log,
(err, authResultsWithTags) => {
// TODO CLDSRV-527 remove ignore for POST object here
if (apiMethod === 'objectPost') {
return next(null, userInfo, authorizationResults, streamingV4Params);
}
if (err) {
log.trace('tag authentication error', { error: err });
return next(err);
Expand Down Expand Up @@ -271,6 +334,12 @@ const api = {
return acc;
}, {});
}
if (apiMethod === 'objectPost' && fileEventData) {
request._response = response;
request.file = fileEventData.file;
return this[apiMethod](userInfo, request, streamingV4Params,
log, callback, authorizationResults);
}
if (apiMethod === 'objectPut' || apiMethod === 'objectPutPart') {
request._response = response;
return this[apiMethod](userInfo, request, streamingV4Params,
Expand Down Expand Up @@ -337,6 +406,7 @@ const api = {
objectCopy,
objectHead,
objectPut,
objectPost,
objectPutACL,
objectPutLegalHold,
objectPutTagging,
Expand Down
4 changes: 4 additions & 0 deletions lib/api/apiUtils/object/createAndStoreObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo,
metadataStoreParams.contentMD5 = constants.emptyFileMd5;
return next(null, null, null);
}
if (request.apiMethod === 'objectPost') {
return dataStore(objectKeyContext, cipherBundle, request.file, size,
streamingV4Params, backendInfo, log, next);
}
return dataStore(objectKeyContext, cipherBundle, request, size,
streamingV4Params, backendInfo, log, next);
},
Expand Down
2 changes: 1 addition & 1 deletion lib/api/apiUtils/object/prepareStream.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const V4Transform = require('../../../auth/streamingV4/V4Transform');
* the type of request requires them
*/
function prepareStream(stream, streamingV4Params, log, errCb) {
if (stream.headers['x-amz-content-sha256'] ===
if (stream.headers && stream.headers['x-amz-content-sha256'] ===
'STREAMING-AWS4-HMAC-SHA256-PAYLOAD') {
if (typeof streamingV4Params !== 'object') {
// this might happen if the user provided a valid V2
Expand Down
Loading