Switch from hashed to random attachment file names

Using hashes, we get the benefit of deduplication but if a user receives two
messages with the same attachment, deleting one would delete it for both since
they are only stored once. To avoid the complexity of tracking number of
references, we simply generate random file names similar to iMessage on MacOS
(?) and Signal Android.
pull/1/head
Daniel Gasienica 7 years ago
parent d9de6dacba
commit 1283c77518

@ -1,7 +1,6 @@
const crypto = require('crypto'); const crypto = require('crypto');
const FSE = require('fs-extra'); const FSE = require('fs-extra');
const isArrayBuffer = require('lodash/isArrayBuffer'); const isArrayBuffer = require('lodash/isArrayBuffer');
const isBuffer = require('lodash/isBuffer');
const isString = require('lodash/isString'); const isString = require('lodash/isString');
const Path = require('path'); const Path = require('path');
@ -17,29 +16,20 @@ exports.writeAttachmentData = (root) => {
} }
const buffer = new Buffer(arrayBuffer); const buffer = new Buffer(arrayBuffer);
const path = Path.join(root, exports._getAttachmentPath(buffer)); const path = Path.join(root, exports._getAttachmentPath());
await FSE.ensureFile(path); await FSE.ensureFile(path);
await FSE.writeFile(path, buffer); await FSE.writeFile(path, buffer);
return path; return path;
}; };
}; };
exports._getAttachmentName = (buffer) => { exports._getAttachmentName = () => {
if (!isBuffer(buffer)) { const buffer = crypto.randomBytes(32);
throw new TypeError('`buffer` must be a buffer'); return buffer.toString('hex');
}
const hash = crypto.createHash('sha256');
hash.update(buffer);
return hash.digest('hex');
}; };
exports._getAttachmentPath = (buffer) => { exports._getAttachmentPath = () => {
if (!isBuffer(buffer)) { const name = exports._getAttachmentName();
throw new TypeError('`buffer` must be a buffer');
}
const name = exports._getAttachmentName(buffer);
const prefix = name.slice(0, 3); const prefix = name.slice(0, 3);
return Path.join(prefix, name); return Path.join(prefix, name);
}; };

@ -2,7 +2,7 @@ const FSE = require('fs-extra');
const isEqual = require('lodash/isEqual'); const isEqual = require('lodash/isEqual');
const Path = require('path'); const Path = require('path');
const stringToArrayBuffer = require('string-to-arraybuffer'); const stringToArrayBuffer = require('string-to-arraybuffer');
const tempy = require('tempy'); const tmp = require('tmp');
const { assert } = require('chai'); const { assert } = require('chai');
const { const {
@ -12,11 +12,15 @@ const {
} = require('../../../../app/types/attachment/write_attachment_data'); } = require('../../../../app/types/attachment/write_attachment_data');
const PREFIX_LENGTH = 3;
const NUM_SEPARATORS = 1;
const NAME_LENGTH = 64;
const PATH_LENGTH = PREFIX_LENGTH + NUM_SEPARATORS + NAME_LENGTH;
describe('writeAttachmentData', () => { describe('writeAttachmentData', () => {
let TEMPORARY_DIRECTORY = null; let TEMPORARY_DIRECTORY = null;
before(() => { before(() => {
// Sync! TEMPORARY_DIRECTORY = tmp.dirSync().name;
TEMPORARY_DIRECTORY = tempy.directory();
}); });
after(async () => { after(async () => {
@ -26,33 +30,25 @@ describe('writeAttachmentData', () => {
it('should write file to disk and return path', async () => { it('should write file to disk and return path', async () => {
const input = stringToArrayBuffer('test string'); const input = stringToArrayBuffer('test string');
const tempDirectory = Path.join(TEMPORARY_DIRECTORY, 'writeAttachmentData'); const tempDirectory = Path.join(TEMPORARY_DIRECTORY, 'writeAttachmentData');
const expectedPath = Path.join(
tempDirectory,
'd55/d5579c46dfcc7f18207013e65b44e4cb4e2c2298f4ac457ba8f82743f31e930b'
);
const outputPath = await writeAttachmentData(tempDirectory)(input); const outputPath = await writeAttachmentData(tempDirectory)(input);
const output = await FSE.readFile(outputPath); const output = await FSE.readFile(outputPath);
assert.strictEqual(outputPath, expectedPath); assert.lengthOf(Path.relative(tempDirectory, outputPath), PATH_LENGTH);
const inputBuffer = Buffer.from(input); const inputBuffer = Buffer.from(input);
assert.isTrue(isEqual(inputBuffer, output)); assert.isTrue(isEqual(inputBuffer, output));
}); });
describe('_getAttachmentName', () => { describe('_getAttachmentName', () => {
it('should return correct name', () => { it('should return random file name with correct length', () => {
const input = Buffer.from('test string', 'utf8'); assert.lengthOf(_getAttachmentName(), NAME_LENGTH);
const expected = 'd5579c46dfcc7f18207013e65b44e4cb4e2c2298f4ac457ba8f82743f31e930b';
assert.strictEqual(_getAttachmentName(input), expected);
}); });
}); });
describe('_getAttachmentPath', () => { describe('_getAttachmentPath', () => {
it('should return correct path', () => { it('should return correct path', () => {
const input = Buffer.from('test string', 'utf8'); assert.lengthOf(_getAttachmentPath(), PATH_LENGTH);
const expected = 'd55/d5579c46dfcc7f18207013e65b44e4cb4e2c2298f4ac457ba8f82743f31e930b';
assert.strictEqual(_getAttachmentPath(input), expected);
}); });
}); });
}); });

Loading…
Cancel
Save