Skip to content

Commit 8730ed5

Browse files
authored
fix(api-attachment): Calculate file content hash when uploading attachment ZMS-172 (#733)
* Added submission api endpoint to api docs generation * on attachment upload calculate file content hash * make stream into separate file, refactor * create stream during the try to store. otherwise getting stuck * refactor file content hash update * safer file content hash handling * refactor code. Fix possible race condition * refactor function. Pass callback as last function param
1 parent 1a12b03 commit 8730ed5

File tree

2 files changed

+79
-4
lines changed

2 files changed

+79
-4
lines changed

lib/attachments/gridstore-storage.js

+41-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const errors = require('../errors');
77
const log = require('npmlog');
88
const crypto = require('crypto');
99
const base64Offset = require('./base64-offset');
10+
const FileHashCalculatorStream = require('../filehash-stream');
1011

1112
// Set to false to disable base64 decoding feature
1213
const FEATURE_DECODE_ATTACHMENTS = true;
@@ -32,6 +33,23 @@ class GridstoreStorage {
3233
});
3334
}
3435

36+
updateFileWithContentHashMetadata(args, hash, calculatedFileContentHash, callback) {
37+
this.gridfs.collection(this.bucketName + '.files').findOneAndUpdate(
38+
{
39+
_id: hash
40+
},
41+
{
42+
$set: {
43+
'metadata.fileContentHash': calculatedFileContentHash
44+
}
45+
},
46+
{
47+
returnDocument: 'after'
48+
},
49+
() => callback(...args) // do not really care about error here. If error then highly likely the file has not been uploaded either
50+
);
51+
}
52+
3553
async get(attachmentId) {
3654
let attachmentData = await this.gridfs.collection(this.bucketName + '.files').findOne({
3755
_id: attachmentId
@@ -129,6 +147,13 @@ class GridstoreStorage {
129147
let storeLock;
130148

131149
let attachmentCallback = (...args) => {
150+
// store finished uploading, add the hash of the file contents to file metadata
151+
let calculatedFileContentHash;
152+
153+
if (args.length > 2) {
154+
calculatedFileContentHash = args[2];
155+
}
156+
132157
if (storeLock) {
133158
log.silly('GridStore', '[%s] UNLOCK lock=%s status=%s', instance, lockId, storeLock.success ? 'locked' : 'empty');
134159
if (storeLock.success) {
@@ -137,7 +162,11 @@ class GridstoreStorage {
137162
// might be already finished if retrying after delay
138163
return;
139164
}
140-
callback(...args);
165+
if (calculatedFileContentHash) {
166+
// locked upload, new file
167+
this.updateFileWithContentHashMetadata(args, hash, calculatedFileContentHash, callback);
168+
return; // return from attachmentCallback. Top level callback will be ran after hash update
169+
}
141170
});
142171
// unset variable to prevent double releasing
143172
storeLock = false;
@@ -149,6 +178,11 @@ class GridstoreStorage {
149178
// might be already finished if retrying after delay
150179
return;
151180
}
181+
if (calculatedFileContentHash) {
182+
// no lock upload, new file
183+
this.updateFileWithContentHashMetadata(args, hash, calculatedFileContentHash, callback);
184+
return; // return from attachmentCallback. Top level callback will be ran after hash update
185+
}
152186
callback(...args);
153187
};
154188

@@ -159,6 +193,8 @@ class GridstoreStorage {
159193
return;
160194
}
161195

196+
let fileHashCalculator = new FileHashCalculatorStream();
197+
162198
this.gridfs.collection(this.bucketName + '.files').findOneAndUpdate(
163199
{
164200
_id: hash
@@ -282,13 +318,14 @@ class GridstoreStorage {
282318
attachmentCallback(err);
283319
});
284320

285-
store.once('finish', () => attachmentCallback(null, id));
321+
store.once('finish', () => attachmentCallback(null, id, fileHashCalculator.hash));
286322

287323
if (!metadata.decoded) {
288-
store.end(attachment.body);
324+
fileHashCalculator.pipe(store);
325+
fileHashCalculator.end(attachment.body);
289326
} else {
290327
let decoder = new libbase64.Decoder();
291-
decoder.pipe(store);
328+
decoder.pipe(fileHashCalculator).pipe(store);
292329
decoder.once('error', err => {
293330
// pass error forward
294331
store.emit('error', err);

lib/filehash-stream.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use strict';
2+
3+
const Transform = require('stream').Transform;
4+
const crypto = require('crypto');
5+
6+
class FileHashCalculatorStream extends Transform {
7+
constructor(options) {
8+
super(options);
9+
this.bodyHash = crypto.createHash('sha256');
10+
this.hash = null;
11+
}
12+
13+
updateHash(chunk) {
14+
this.bodyHash.update(chunk);
15+
}
16+
17+
_transform(chunk, encoding, callback) {
18+
if (!chunk || !chunk.length) {
19+
return callback();
20+
}
21+
22+
if (typeof chunk === 'string') {
23+
chunk = Buffer.from(chunk, encoding);
24+
}
25+
26+
this.updateHash(chunk);
27+
this.push(chunk);
28+
29+
callback();
30+
}
31+
32+
_flush(done) {
33+
this.hash = this.bodyHash.digest('base64');
34+
done();
35+
}
36+
}
37+
38+
module.exports = FileHashCalculatorStream;

0 commit comments

Comments
 (0)