-
Notifications
You must be signed in to change notification settings - Fork 5
/
zip-crypto-stream.js
140 lines (116 loc) · 4.54 KB
/
zip-crypto-stream.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
'use strict';
const inherits = require('util').inherits;
const CryptoStream = require('./crypto-stream');
const ZipStream = require('zip-stream');
const {DeflateCRC32Stream, CRC32Stream} = require('crc32-stream');
// var crc32 = require('buffer-crc32');
const constants = require('compress-commons/lib/archivers/zip/constants');
/**
* Overrides ZipStream with ZipCrypto/Zip2.0 encryption
*/
const ZipCryptoStream = function (options = {zlib: {}}) {
if (!(this instanceof ZipCryptoStream)) {
return new ZipCryptoStream(options);
}
this.key = options.password;
if (!Buffer.isBuffer(this.key)) {
this.key = Buffer.from(options.password);
}
ZipStream.call(this, options);
};
inherits(ZipCryptoStream, ZipStream);
ZipCryptoStream.prototype._writeLocalFileHeader = function (ae) {
// set encryption
ae.getGeneralPurposeBit().useEncryption(true);
ZipStream.prototype._writeLocalFileHeader.call(this, ae);
};
/**
* As opposed to original implementation which streamed deflated data into target stream,
* this implementation buffers deflated data. This is done for 2 reasons:
* 1) encryption header is prepended to encrypted file datamust include bytes from file's CRC
* 2) using data descriptor along with ZipCrypto encryption seems unsupported (this may be
* the consequence of 1st reason but I don't know that for sure)
*
* The buffering currently done in memory. If a need arises, need to add disk buffering.
*/
ZipCryptoStream.prototype._smartStream = function (ae, callback) {
let deflate = ae.getMethod() === constants.METHOD_DEFLATED;
let compressionStream = deflate ? new DeflateCRC32Stream(this.options.zlib) : new CRC32Stream();
let error = null;
let bufferedData = [];
compressionStream.once('error', function (err) {
error = err;
});
compressionStream.on('data', (data) => {
bufferedData.push(data);
});
compressionStream.once('end', () => {
let crc = compressionStream.digest();
// gather complete information for CRC and sizes
ae.setCrc(crc.readUInt32BE(0));
ae.setSize(compressionStream.size());
ae.setCompressedSize(compressionStream.size(true) + 12);
// write local header now
this._writeLocalFileHeader(ae);
// write all buffered data to encrypt stream
let encrypt = new CryptoStream({key: this.key, crc});
let writes = [];
for (let chunk of bufferedData) {
writes.push(() => writeBufferToStream(encrypt, chunk));
}
promiseSerial(writes).then(() => encrypt.end());
encrypt.once('end', () => {
this._afterAppend(ae);
callback(error, ae);
});
encrypt.once('error', function (err) {
error = err;
});
encrypt.pipe(this, {end: false});
});
return compressionStream;
};
ZipCryptoStream.prototype._appendBuffer = function(ae, source, callback) {
ae.setSize(source.length);
ae.setCompressedSize(source.length);
ae.getGeneralPurposeBit().useDataDescriptor(false);
// we will write local file header after we get CRC back
// it seems as if using data descriptor is not supported when encrypting data with ZipCrypto
// so we have to write CRC into local file header
// this._writeLocalFileHeader(ae);
this._smartStream(ae, callback).end(source);
};
ZipCryptoStream.prototype._appendStream = function(ae, source, callback) {
ae.setVersionNeededToExtract(constants.MIN_VERSION_DATA_DESCRIPTOR);
ae.getGeneralPurposeBit().useDataDescriptor(false);
// we will write local file header after we get CRC back
// it seems as if using data descriptor is not supported when encrypting data with ZipCrypto
// so we have to write CRC into local file header
// this._writeLocalFileHeader(ae);
var smart = this._smartStream(ae, callback);
source.once('error', function(err) {
smart.emit('error', err);
smart.end();
});
source.pipe(smart);
};
function writeBufferToStream(writable, data) {
return new Promise((resolve, reject) => {
writable.write(data, (err) => {
if (err) {
reject();
} else {
resolve();
}
});
});
}
/**
* Execute promises sequentially. Input is an array of functions that return promises to execute.
*/
const promiseSerial = funcs =>
funcs.reduce((promise, func) =>
promise.then(result =>
func().then(Array.prototype.concat.bind(result))),
Promise.resolve([]));
module.exports = ZipCryptoStream;