Skip to content

Commit f6d3c83

Browse files
author
Will Toozs
committed
CLDSRV-546: post object action
1 parent ea1de5a commit f6d3c83

File tree

2 files changed

+146
-2
lines changed

2 files changed

+146
-2
lines changed

lib/api/apiUtils/object/createAndStoreObject.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,18 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo,
210210
metadataStoreParams.contentMD5 = constants.emptyFileMd5;
211211
return next(null, null, null);
212212
}
213-
return dataStore(objectKeyContext, cipherBundle, request, size,
214-
streamingV4Params, backendInfo, log, next);
213+
// Object Post receives a file stream.
214+
// This is to be used to store data instead of the request stream itself.
215+
216+
let stream;
217+
218+
if (request.apiMethod === 'objectPost') {
219+
stream = request.fileEventData ? request.fileEventData.file : undefined;
220+
} else {
221+
stream = request;
222+
}
223+
224+
return dataStore(objectKeyContext, cipherBundle, stream, size, streamingV4Params, backendInfo, log, next);
215225
},
216226
function processDataResult(dataGetInfo, calculatedHash, next) {
217227
if (dataGetInfo === null || dataGetInfo === undefined) {

lib/api/objectPost.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
const async = require('async');
2+
const { errors, versioning } = require('arsenal');
3+
const { PassThrough } = require('stream');
4+
5+
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
6+
const createAndStoreObject = require('./apiUtils/object/createAndStoreObject');
7+
const { standardMetadataValidateBucketAndObj } = require('../metadata/metadataUtils');
8+
const { config } = require('../Config');
9+
const { setExpirationHeaders } = require('./apiUtils/object/expirationHeaders');
10+
const writeContinue = require('../utilities/writeContinue');
11+
const { overheadField } = require('../../constants');
12+
13+
14+
const versionIdUtils = versioning.VersionID;
15+
16+
17+
/**
18+
* POST Object in the requested bucket. Steps include:
19+
* validating metadata for authorization, bucket and object existence etc.
20+
* store object data in datastore upon successful authorization
21+
* store object location returned by datastore and
22+
* object's (custom) headers in metadata
23+
* return the result in final callback
24+
*
25+
* @param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info
26+
* @param {request} request - request object given by router,
27+
* includes normalized headers
28+
* @param {object | undefined } streamingV4Params - if v4 auth,
29+
* object containing accessKey, signatureFromRequest, region, scopeDate,
30+
* timestamp, and credentialScope
31+
* (to be used for streaming v4 auth if applicable)
32+
* @param {object} log - the log request
33+
* @param {Function} callback - final callback to call with the result
34+
* @return {undefined}
35+
*/
36+
function objectPost(authInfo, request, streamingV4Params, log, callback) {
37+
const {
38+
headers,
39+
method,
40+
} = request;
41+
let parsedContentLength = 0;
42+
const passThroughStream = new PassThrough();
43+
const requestType = request.apiMethods || 'objectPost';
44+
const valParams = {
45+
authInfo,
46+
bucketName: request.formData.bucket,
47+
objectKey: request.formData.key,
48+
requestType,
49+
request,
50+
};
51+
const canonicalID = authInfo.getCanonicalID();
52+
53+
54+
log.trace('owner canonicalID to send to data', { canonicalID });
55+
return standardMetadataValidateBucketAndObj(valParams, request.actionImplicitDenies, log,
56+
(err, bucket, objMD) => {
57+
const responseHeaders = collectCorsHeaders(headers.origin,
58+
method, bucket);
59+
60+
if (err && !err.AccessDenied) {
61+
log.trace('error processing request', {
62+
error: err,
63+
method: 'metadataValidateBucketAndObj',
64+
});
65+
return callback(err, responseHeaders);
66+
}
67+
if (bucket.hasDeletedFlag() && canonicalID !== bucket.getOwner()) {
68+
log.trace('deleted flag on bucket and request ' +
69+
'from non-owner account');
70+
return callback(errors.NoSuchBucket);
71+
}
72+
73+
return async.waterfall([
74+
function countPOSTFileSize(next) {
75+
if (!request.fileEventData || !request.fileEventData.file) {
76+
return next();
77+
}
78+
request.fileEventData.file.on('data', (chunk) => {
79+
parsedContentLength += chunk.length;
80+
passThroughStream.write(chunk);
81+
});
82+
83+
request.fileEventData.file.on('end', () => {
84+
// Here totalBytes will have the total size of the file
85+
passThroughStream.end();
86+
// Setting the file in the request avoids the need to make changes to createAndStoreObject's
87+
// parameters and thus all it's subsequent calls. This is necessary as the stream used to create
88+
// the object is that of the request directly; something we must work around
89+
// to use the file data produced from the multipart form data.
90+
/* eslint-disable no-param-reassign */
91+
request.fileEventData.file = passThroughStream;
92+
/* eslint-disable no-param-reassign */
93+
request.parsedContentLength = parsedContentLength;
94+
return next();
95+
});
96+
return undefined;
97+
},
98+
function objectCreateAndStore(next) {
99+
writeContinue(request, request._response);
100+
return createAndStoreObject(request.bucketName,
101+
bucket, request.formData.key, objMD, authInfo, canonicalID, null,
102+
request, false, streamingV4Params, overheadField, log, next);
103+
},
104+
], (err, storingResult) => {
105+
if (err) {
106+
return callback(err, responseHeaders);
107+
}
108+
setExpirationHeaders(responseHeaders, {
109+
lifecycleConfig: bucket.getLifecycleConfiguration(),
110+
objectParams: {
111+
key: request.key,
112+
date: storingResult.lastModified,
113+
tags: storingResult.tags,
114+
},
115+
});
116+
if (storingResult) {
117+
// ETag's hex should always be enclosed in quotes
118+
responseHeaders.ETag = `"${storingResult.contentMD5}"`;
119+
}
120+
const vcfg = bucket.getVersioningConfiguration();
121+
const isVersionedObj = vcfg && vcfg.Status === 'Enabled';
122+
if (isVersionedObj) {
123+
if (storingResult && storingResult.versionId) {
124+
responseHeaders['x-amz-version-id'] =
125+
versionIdUtils.encode(storingResult.versionId,
126+
config.versionIdEncodingType);
127+
}
128+
}
129+
return callback(null, responseHeaders);
130+
});
131+
});
132+
}
133+
134+
module.exports = objectPost;

0 commit comments

Comments
 (0)