polikolog 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/.idea/5lab.iml +12 -0
- package/.idea/inspectionProfiles/Project_Default.xml +10 -0
- package/.idea/jsLibraryMappings.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/06-02.js +48 -0
- package/06-03.js +22 -0
- package/06-04.js +22 -0
- package/index.html +41 -0
- package/m0603.js +28 -0
- package/mypackage/m0603.js +28 -0
- package/mypackage/node_modules/.package-lock.json +24 -0
- package/mypackage/node_modules/nodemailer/.gitattributes +6 -0
- package/mypackage/node_modules/nodemailer/.prettierrc.js +8 -0
- package/mypackage/node_modules/nodemailer/CHANGELOG.md +725 -0
- package/mypackage/node_modules/nodemailer/CODE_OF_CONDUCT.md +76 -0
- package/mypackage/node_modules/nodemailer/CONTRIBUTING.md +67 -0
- package/mypackage/node_modules/nodemailer/LICENSE +16 -0
- package/mypackage/node_modules/nodemailer/README.md +97 -0
- package/mypackage/node_modules/nodemailer/SECURITY.txt +22 -0
- package/mypackage/node_modules/nodemailer/lib/addressparser/index.js +313 -0
- package/mypackage/node_modules/nodemailer/lib/base64/index.js +142 -0
- package/mypackage/node_modules/nodemailer/lib/dkim/index.js +251 -0
- package/mypackage/node_modules/nodemailer/lib/dkim/message-parser.js +155 -0
- package/mypackage/node_modules/nodemailer/lib/dkim/relaxed-body.js +154 -0
- package/mypackage/node_modules/nodemailer/lib/dkim/sign.js +117 -0
- package/mypackage/node_modules/nodemailer/lib/fetch/cookies.js +281 -0
- package/mypackage/node_modules/nodemailer/lib/fetch/index.js +274 -0
- package/mypackage/node_modules/nodemailer/lib/json-transport/index.js +82 -0
- package/mypackage/node_modules/nodemailer/lib/mail-composer/index.js +558 -0
- package/mypackage/node_modules/nodemailer/lib/mailer/index.js +427 -0
- package/mypackage/node_modules/nodemailer/lib/mailer/mail-message.js +315 -0
- package/mypackage/node_modules/nodemailer/lib/mime-funcs/index.js +625 -0
- package/mypackage/node_modules/nodemailer/lib/mime-funcs/mime-types.js +2102 -0
- package/mypackage/node_modules/nodemailer/lib/mime-node/index.js +1290 -0
- package/mypackage/node_modules/nodemailer/lib/mime-node/last-newline.js +33 -0
- package/mypackage/node_modules/nodemailer/lib/mime-node/le-unix.js +43 -0
- package/mypackage/node_modules/nodemailer/lib/mime-node/le-windows.js +52 -0
- package/mypackage/node_modules/nodemailer/lib/nodemailer.js +143 -0
- package/mypackage/node_modules/nodemailer/lib/qp/index.js +219 -0
- package/mypackage/node_modules/nodemailer/lib/sendmail-transport/index.js +210 -0
- package/mypackage/node_modules/nodemailer/lib/ses-transport/index.js +349 -0
- package/mypackage/node_modules/nodemailer/lib/shared/index.js +638 -0
- package/mypackage/node_modules/nodemailer/lib/smtp-connection/data-stream.js +108 -0
- package/mypackage/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +143 -0
- package/mypackage/node_modules/nodemailer/lib/smtp-connection/index.js +1796 -0
- package/mypackage/node_modules/nodemailer/lib/smtp-pool/index.js +648 -0
- package/mypackage/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +253 -0
- package/mypackage/node_modules/nodemailer/lib/smtp-transport/index.js +416 -0
- package/mypackage/node_modules/nodemailer/lib/stream-transport/index.js +135 -0
- package/mypackage/node_modules/nodemailer/lib/well-known/index.js +47 -0
- package/mypackage/node_modules/nodemailer/lib/well-known/services.json +286 -0
- package/mypackage/node_modules/nodemailer/lib/xoauth2/index.js +376 -0
- package/mypackage/node_modules/nodemailer/package.json +46 -0
- package/mypackage/node_modules/nodemailer/postinstall.js +101 -0
- package/mypackage/package.json +15 -0
- package/package.json +15 -0
@@ -0,0 +1,142 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const Transform = require('stream').Transform;
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Encodes a Buffer into a base64 encoded string
|
7
|
+
*
|
8
|
+
* @param {Buffer} buffer Buffer to convert
|
9
|
+
* @returns {String} base64 encoded string
|
10
|
+
*/
|
11
|
+
function encode(buffer) {
|
12
|
+
if (typeof buffer === 'string') {
|
13
|
+
buffer = Buffer.from(buffer, 'utf-8');
|
14
|
+
}
|
15
|
+
|
16
|
+
return buffer.toString('base64');
|
17
|
+
}
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Adds soft line breaks to a base64 string
|
21
|
+
*
|
22
|
+
* @param {String} str base64 encoded string that might need line wrapping
|
23
|
+
* @param {Number} [lineLength=76] Maximum allowed length for a line
|
24
|
+
* @returns {String} Soft-wrapped base64 encoded string
|
25
|
+
*/
|
26
|
+
function wrap(str, lineLength) {
|
27
|
+
str = (str || '').toString();
|
28
|
+
lineLength = lineLength || 76;
|
29
|
+
|
30
|
+
if (str.length <= lineLength) {
|
31
|
+
return str;
|
32
|
+
}
|
33
|
+
|
34
|
+
let result = [];
|
35
|
+
let pos = 0;
|
36
|
+
let chunkLength = lineLength * 1024;
|
37
|
+
while (pos < str.length) {
|
38
|
+
let wrappedLines = str
|
39
|
+
.substr(pos, chunkLength)
|
40
|
+
.replace(new RegExp('.{' + lineLength + '}', 'g'), '$&\r\n')
|
41
|
+
.trim();
|
42
|
+
result.push(wrappedLines);
|
43
|
+
pos += chunkLength;
|
44
|
+
}
|
45
|
+
|
46
|
+
return result.join('\r\n').trim();
|
47
|
+
}
|
48
|
+
|
49
|
+
/**
|
50
|
+
* Creates a transform stream for encoding data to base64 encoding
|
51
|
+
*
|
52
|
+
* @constructor
|
53
|
+
* @param {Object} options Stream options
|
54
|
+
* @param {Number} [options.lineLength=76] Maximum length for lines, set to false to disable wrapping
|
55
|
+
*/
|
56
|
+
class Encoder extends Transform {
|
57
|
+
constructor(options) {
|
58
|
+
super();
|
59
|
+
// init Transform
|
60
|
+
this.options = options || {};
|
61
|
+
|
62
|
+
if (this.options.lineLength !== false) {
|
63
|
+
this.options.lineLength = this.options.lineLength || 76;
|
64
|
+
}
|
65
|
+
|
66
|
+
this._curLine = '';
|
67
|
+
this._remainingBytes = false;
|
68
|
+
|
69
|
+
this.inputBytes = 0;
|
70
|
+
this.outputBytes = 0;
|
71
|
+
}
|
72
|
+
|
73
|
+
_transform(chunk, encoding, done) {
|
74
|
+
if (encoding !== 'buffer') {
|
75
|
+
chunk = Buffer.from(chunk, encoding);
|
76
|
+
}
|
77
|
+
|
78
|
+
if (!chunk || !chunk.length) {
|
79
|
+
return setImmediate(done);
|
80
|
+
}
|
81
|
+
|
82
|
+
this.inputBytes += chunk.length;
|
83
|
+
|
84
|
+
if (this._remainingBytes && this._remainingBytes.length) {
|
85
|
+
chunk = Buffer.concat([this._remainingBytes, chunk], this._remainingBytes.length + chunk.length);
|
86
|
+
this._remainingBytes = false;
|
87
|
+
}
|
88
|
+
|
89
|
+
if (chunk.length % 3) {
|
90
|
+
this._remainingBytes = chunk.slice(chunk.length - (chunk.length % 3));
|
91
|
+
chunk = chunk.slice(0, chunk.length - (chunk.length % 3));
|
92
|
+
} else {
|
93
|
+
this._remainingBytes = false;
|
94
|
+
}
|
95
|
+
|
96
|
+
let b64 = this._curLine + encode(chunk);
|
97
|
+
|
98
|
+
if (this.options.lineLength) {
|
99
|
+
b64 = wrap(b64, this.options.lineLength);
|
100
|
+
|
101
|
+
// remove last line as it is still most probably incomplete
|
102
|
+
let lastLF = b64.lastIndexOf('\n');
|
103
|
+
if (lastLF < 0) {
|
104
|
+
this._curLine = b64;
|
105
|
+
b64 = '';
|
106
|
+
} else if (lastLF === b64.length - 1) {
|
107
|
+
this._curLine = '';
|
108
|
+
} else {
|
109
|
+
this._curLine = b64.substr(lastLF + 1);
|
110
|
+
b64 = b64.substr(0, lastLF + 1);
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
if (b64) {
|
115
|
+
this.outputBytes += b64.length;
|
116
|
+
this.push(Buffer.from(b64, 'ascii'));
|
117
|
+
}
|
118
|
+
|
119
|
+
setImmediate(done);
|
120
|
+
}
|
121
|
+
|
122
|
+
_flush(done) {
|
123
|
+
if (this._remainingBytes && this._remainingBytes.length) {
|
124
|
+
this._curLine += encode(this._remainingBytes);
|
125
|
+
}
|
126
|
+
|
127
|
+
if (this._curLine) {
|
128
|
+
this._curLine = wrap(this._curLine, this.options.lineLength);
|
129
|
+
this.outputBytes += this._curLine.length;
|
130
|
+
this.push(this._curLine, 'ascii');
|
131
|
+
this._curLine = '';
|
132
|
+
}
|
133
|
+
done();
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
// expose to the world
|
138
|
+
module.exports = {
|
139
|
+
encode,
|
140
|
+
wrap,
|
141
|
+
Encoder
|
142
|
+
};
|
@@ -0,0 +1,251 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
// FIXME:
|
4
|
+
// replace this Transform mess with a method that pipes input argument to output argument
|
5
|
+
|
6
|
+
const MessageParser = require('./message-parser');
|
7
|
+
const RelaxedBody = require('./relaxed-body');
|
8
|
+
const sign = require('./sign');
|
9
|
+
const PassThrough = require('stream').PassThrough;
|
10
|
+
const fs = require('fs');
|
11
|
+
const path = require('path');
|
12
|
+
const crypto = require('crypto');
|
13
|
+
|
14
|
+
const DKIM_ALGO = 'sha256';
|
15
|
+
const MAX_MESSAGE_SIZE = 128 * 1024; // buffer messages larger than this to disk
|
16
|
+
|
17
|
+
/*
|
18
|
+
// Usage:
|
19
|
+
|
20
|
+
let dkim = new DKIM({
|
21
|
+
domainName: 'example.com',
|
22
|
+
keySelector: 'key-selector',
|
23
|
+
privateKey,
|
24
|
+
cacheDir: '/tmp'
|
25
|
+
});
|
26
|
+
dkim.sign(input).pipe(process.stdout);
|
27
|
+
|
28
|
+
// Where inputStream is a rfc822 message (either a stream, string or Buffer)
|
29
|
+
// and outputStream is a DKIM signed rfc822 message
|
30
|
+
*/
|
31
|
+
|
32
|
+
class DKIMSigner {
|
33
|
+
constructor(options, keys, input, output) {
|
34
|
+
this.options = options || {};
|
35
|
+
this.keys = keys;
|
36
|
+
|
37
|
+
this.cacheTreshold = Number(this.options.cacheTreshold) || MAX_MESSAGE_SIZE;
|
38
|
+
this.hashAlgo = this.options.hashAlgo || DKIM_ALGO;
|
39
|
+
|
40
|
+
this.cacheDir = this.options.cacheDir || false;
|
41
|
+
|
42
|
+
this.chunks = [];
|
43
|
+
this.chunklen = 0;
|
44
|
+
this.readPos = 0;
|
45
|
+
this.cachePath = this.cacheDir ? path.join(this.cacheDir, 'message.' + Date.now() + '-' + crypto.randomBytes(14).toString('hex')) : false;
|
46
|
+
this.cache = false;
|
47
|
+
|
48
|
+
this.headers = false;
|
49
|
+
this.bodyHash = false;
|
50
|
+
this.parser = false;
|
51
|
+
this.relaxedBody = false;
|
52
|
+
|
53
|
+
this.input = input;
|
54
|
+
this.output = output;
|
55
|
+
this.output.usingCache = false;
|
56
|
+
|
57
|
+
this.hasErrored = false;
|
58
|
+
|
59
|
+
this.input.on('error', err => {
|
60
|
+
this.hasErrored = true;
|
61
|
+
this.cleanup();
|
62
|
+
output.emit('error', err);
|
63
|
+
});
|
64
|
+
}
|
65
|
+
|
66
|
+
cleanup() {
|
67
|
+
if (!this.cache || !this.cachePath) {
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
fs.unlink(this.cachePath, () => false);
|
71
|
+
}
|
72
|
+
|
73
|
+
createReadCache() {
|
74
|
+
// pipe remainings to cache file
|
75
|
+
this.cache = fs.createReadStream(this.cachePath);
|
76
|
+
this.cache.once('error', err => {
|
77
|
+
this.cleanup();
|
78
|
+
this.output.emit('error', err);
|
79
|
+
});
|
80
|
+
this.cache.once('close', () => {
|
81
|
+
this.cleanup();
|
82
|
+
});
|
83
|
+
this.cache.pipe(this.output);
|
84
|
+
}
|
85
|
+
|
86
|
+
sendNextChunk() {
|
87
|
+
if (this.hasErrored) {
|
88
|
+
return;
|
89
|
+
}
|
90
|
+
|
91
|
+
if (this.readPos >= this.chunks.length) {
|
92
|
+
if (!this.cache) {
|
93
|
+
return this.output.end();
|
94
|
+
}
|
95
|
+
return this.createReadCache();
|
96
|
+
}
|
97
|
+
let chunk = this.chunks[this.readPos++];
|
98
|
+
if (this.output.write(chunk) === false) {
|
99
|
+
return this.output.once('drain', () => {
|
100
|
+
this.sendNextChunk();
|
101
|
+
});
|
102
|
+
}
|
103
|
+
setImmediate(() => this.sendNextChunk());
|
104
|
+
}
|
105
|
+
|
106
|
+
sendSignedOutput() {
|
107
|
+
let keyPos = 0;
|
108
|
+
let signNextKey = () => {
|
109
|
+
if (keyPos >= this.keys.length) {
|
110
|
+
this.output.write(this.parser.rawHeaders);
|
111
|
+
return setImmediate(() => this.sendNextChunk());
|
112
|
+
}
|
113
|
+
let key = this.keys[keyPos++];
|
114
|
+
let dkimField = sign(this.headers, this.hashAlgo, this.bodyHash, {
|
115
|
+
domainName: key.domainName,
|
116
|
+
keySelector: key.keySelector,
|
117
|
+
privateKey: key.privateKey,
|
118
|
+
headerFieldNames: this.options.headerFieldNames,
|
119
|
+
skipFields: this.options.skipFields
|
120
|
+
});
|
121
|
+
if (dkimField) {
|
122
|
+
this.output.write(Buffer.from(dkimField + '\r\n'));
|
123
|
+
}
|
124
|
+
return setImmediate(signNextKey);
|
125
|
+
};
|
126
|
+
|
127
|
+
if (this.bodyHash && this.headers) {
|
128
|
+
return signNextKey();
|
129
|
+
}
|
130
|
+
|
131
|
+
this.output.write(this.parser.rawHeaders);
|
132
|
+
this.sendNextChunk();
|
133
|
+
}
|
134
|
+
|
135
|
+
createWriteCache() {
|
136
|
+
this.output.usingCache = true;
|
137
|
+
// pipe remainings to cache file
|
138
|
+
this.cache = fs.createWriteStream(this.cachePath);
|
139
|
+
this.cache.once('error', err => {
|
140
|
+
this.cleanup();
|
141
|
+
// drain input
|
142
|
+
this.relaxedBody.unpipe(this.cache);
|
143
|
+
this.relaxedBody.on('readable', () => {
|
144
|
+
while (this.relaxedBody.read() !== null) {
|
145
|
+
// do nothing
|
146
|
+
}
|
147
|
+
});
|
148
|
+
this.hasErrored = true;
|
149
|
+
// emit error
|
150
|
+
this.output.emit('error', err);
|
151
|
+
});
|
152
|
+
this.cache.once('close', () => {
|
153
|
+
this.sendSignedOutput();
|
154
|
+
});
|
155
|
+
this.relaxedBody.removeAllListeners('readable');
|
156
|
+
this.relaxedBody.pipe(this.cache);
|
157
|
+
}
|
158
|
+
|
159
|
+
signStream() {
|
160
|
+
this.parser = new MessageParser();
|
161
|
+
this.relaxedBody = new RelaxedBody({
|
162
|
+
hashAlgo: this.hashAlgo
|
163
|
+
});
|
164
|
+
|
165
|
+
this.parser.on('headers', value => {
|
166
|
+
this.headers = value;
|
167
|
+
});
|
168
|
+
|
169
|
+
this.relaxedBody.on('hash', value => {
|
170
|
+
this.bodyHash = value;
|
171
|
+
});
|
172
|
+
|
173
|
+
this.relaxedBody.on('readable', () => {
|
174
|
+
let chunk;
|
175
|
+
if (this.cache) {
|
176
|
+
return;
|
177
|
+
}
|
178
|
+
while ((chunk = this.relaxedBody.read()) !== null) {
|
179
|
+
this.chunks.push(chunk);
|
180
|
+
this.chunklen += chunk.length;
|
181
|
+
if (this.chunklen >= this.cacheTreshold && this.cachePath) {
|
182
|
+
return this.createWriteCache();
|
183
|
+
}
|
184
|
+
}
|
185
|
+
});
|
186
|
+
|
187
|
+
this.relaxedBody.on('end', () => {
|
188
|
+
if (this.cache) {
|
189
|
+
return;
|
190
|
+
}
|
191
|
+
this.sendSignedOutput();
|
192
|
+
});
|
193
|
+
|
194
|
+
this.parser.pipe(this.relaxedBody);
|
195
|
+
setImmediate(() => this.input.pipe(this.parser));
|
196
|
+
}
|
197
|
+
}
|
198
|
+
|
199
|
+
class DKIM {
|
200
|
+
constructor(options) {
|
201
|
+
this.options = options || {};
|
202
|
+
this.keys = [].concat(
|
203
|
+
this.options.keys || {
|
204
|
+
domainName: options.domainName,
|
205
|
+
keySelector: options.keySelector,
|
206
|
+
privateKey: options.privateKey
|
207
|
+
}
|
208
|
+
);
|
209
|
+
}
|
210
|
+
|
211
|
+
sign(input, extraOptions) {
|
212
|
+
let output = new PassThrough();
|
213
|
+
let inputStream = input;
|
214
|
+
let writeValue = false;
|
215
|
+
|
216
|
+
if (Buffer.isBuffer(input)) {
|
217
|
+
writeValue = input;
|
218
|
+
inputStream = new PassThrough();
|
219
|
+
} else if (typeof input === 'string') {
|
220
|
+
writeValue = Buffer.from(input);
|
221
|
+
inputStream = new PassThrough();
|
222
|
+
}
|
223
|
+
|
224
|
+
let options = this.options;
|
225
|
+
if (extraOptions && Object.keys(extraOptions).length) {
|
226
|
+
options = {};
|
227
|
+
Object.keys(this.options || {}).forEach(key => {
|
228
|
+
options[key] = this.options[key];
|
229
|
+
});
|
230
|
+
Object.keys(extraOptions || {}).forEach(key => {
|
231
|
+
if (!(key in options)) {
|
232
|
+
options[key] = extraOptions[key];
|
233
|
+
}
|
234
|
+
});
|
235
|
+
}
|
236
|
+
|
237
|
+
let signer = new DKIMSigner(options, this.keys, inputStream, output);
|
238
|
+
setImmediate(() => {
|
239
|
+
signer.signStream();
|
240
|
+
if (writeValue) {
|
241
|
+
setImmediate(() => {
|
242
|
+
inputStream.end(writeValue);
|
243
|
+
});
|
244
|
+
}
|
245
|
+
});
|
246
|
+
|
247
|
+
return output;
|
248
|
+
}
|
249
|
+
}
|
250
|
+
|
251
|
+
module.exports = DKIM;
|
@@ -0,0 +1,155 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const Transform = require('stream').Transform;
|
4
|
+
|
5
|
+
/**
|
6
|
+
* MessageParser instance is a transform stream that separates message headers
|
7
|
+
* from the rest of the body. Headers are emitted with the 'headers' event. Message
|
8
|
+
* body is passed on as the resulting stream.
|
9
|
+
*/
|
10
|
+
class MessageParser extends Transform {
|
11
|
+
constructor(options) {
|
12
|
+
super(options);
|
13
|
+
this.lastBytes = Buffer.alloc(4);
|
14
|
+
this.headersParsed = false;
|
15
|
+
this.headerBytes = 0;
|
16
|
+
this.headerChunks = [];
|
17
|
+
this.rawHeaders = false;
|
18
|
+
this.bodySize = 0;
|
19
|
+
}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Keeps count of the last 4 bytes in order to detect line breaks on chunk boundaries
|
23
|
+
*
|
24
|
+
* @param {Buffer} data Next data chunk from the stream
|
25
|
+
*/
|
26
|
+
updateLastBytes(data) {
|
27
|
+
let lblen = this.lastBytes.length;
|
28
|
+
let nblen = Math.min(data.length, lblen);
|
29
|
+
|
30
|
+
// shift existing bytes
|
31
|
+
for (let i = 0, len = lblen - nblen; i < len; i++) {
|
32
|
+
this.lastBytes[i] = this.lastBytes[i + nblen];
|
33
|
+
}
|
34
|
+
|
35
|
+
// add new bytes
|
36
|
+
for (let i = 1; i <= nblen; i++) {
|
37
|
+
this.lastBytes[lblen - i] = data[data.length - i];
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Finds and removes message headers from the remaining body. We want to keep
|
43
|
+
* headers separated until final delivery to be able to modify these
|
44
|
+
*
|
45
|
+
* @param {Buffer} data Next chunk of data
|
46
|
+
* @return {Boolean} Returns true if headers are already found or false otherwise
|
47
|
+
*/
|
48
|
+
checkHeaders(data) {
|
49
|
+
if (this.headersParsed) {
|
50
|
+
return true;
|
51
|
+
}
|
52
|
+
|
53
|
+
let lblen = this.lastBytes.length;
|
54
|
+
let headerPos = 0;
|
55
|
+
this.curLinePos = 0;
|
56
|
+
for (let i = 0, len = this.lastBytes.length + data.length; i < len; i++) {
|
57
|
+
let chr;
|
58
|
+
if (i < lblen) {
|
59
|
+
chr = this.lastBytes[i];
|
60
|
+
} else {
|
61
|
+
chr = data[i - lblen];
|
62
|
+
}
|
63
|
+
if (chr === 0x0a && i) {
|
64
|
+
let pr1 = i - 1 < lblen ? this.lastBytes[i - 1] : data[i - 1 - lblen];
|
65
|
+
let pr2 = i > 1 ? (i - 2 < lblen ? this.lastBytes[i - 2] : data[i - 2 - lblen]) : false;
|
66
|
+
if (pr1 === 0x0a) {
|
67
|
+
this.headersParsed = true;
|
68
|
+
headerPos = i - lblen + 1;
|
69
|
+
this.headerBytes += headerPos;
|
70
|
+
break;
|
71
|
+
} else if (pr1 === 0x0d && pr2 === 0x0a) {
|
72
|
+
this.headersParsed = true;
|
73
|
+
headerPos = i - lblen + 1;
|
74
|
+
this.headerBytes += headerPos;
|
75
|
+
break;
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
if (this.headersParsed) {
|
81
|
+
this.headerChunks.push(data.slice(0, headerPos));
|
82
|
+
this.rawHeaders = Buffer.concat(this.headerChunks, this.headerBytes);
|
83
|
+
this.headerChunks = null;
|
84
|
+
this.emit('headers', this.parseHeaders());
|
85
|
+
if (data.length - 1 > headerPos) {
|
86
|
+
let chunk = data.slice(headerPos);
|
87
|
+
this.bodySize += chunk.length;
|
88
|
+
// this would be the first chunk of data sent downstream
|
89
|
+
setImmediate(() => this.push(chunk));
|
90
|
+
}
|
91
|
+
return false;
|
92
|
+
} else {
|
93
|
+
this.headerBytes += data.length;
|
94
|
+
this.headerChunks.push(data);
|
95
|
+
}
|
96
|
+
|
97
|
+
// store last 4 bytes to catch header break
|
98
|
+
this.updateLastBytes(data);
|
99
|
+
|
100
|
+
return false;
|
101
|
+
}
|
102
|
+
|
103
|
+
_transform(chunk, encoding, callback) {
|
104
|
+
if (!chunk || !chunk.length) {
|
105
|
+
return callback();
|
106
|
+
}
|
107
|
+
|
108
|
+
if (typeof chunk === 'string') {
|
109
|
+
chunk = Buffer.from(chunk, encoding);
|
110
|
+
}
|
111
|
+
|
112
|
+
let headersFound;
|
113
|
+
|
114
|
+
try {
|
115
|
+
headersFound = this.checkHeaders(chunk);
|
116
|
+
} catch (E) {
|
117
|
+
return callback(E);
|
118
|
+
}
|
119
|
+
|
120
|
+
if (headersFound) {
|
121
|
+
this.bodySize += chunk.length;
|
122
|
+
this.push(chunk);
|
123
|
+
}
|
124
|
+
|
125
|
+
setImmediate(callback);
|
126
|
+
}
|
127
|
+
|
128
|
+
_flush(callback) {
|
129
|
+
if (this.headerChunks) {
|
130
|
+
let chunk = Buffer.concat(this.headerChunks, this.headerBytes);
|
131
|
+
this.bodySize += chunk.length;
|
132
|
+
this.push(chunk);
|
133
|
+
this.headerChunks = null;
|
134
|
+
}
|
135
|
+
callback();
|
136
|
+
}
|
137
|
+
|
138
|
+
parseHeaders() {
|
139
|
+
let lines = (this.rawHeaders || '').toString().split(/\r?\n/);
|
140
|
+
for (let i = lines.length - 1; i > 0; i--) {
|
141
|
+
if (/^\s/.test(lines[i])) {
|
142
|
+
lines[i - 1] += '\n' + lines[i];
|
143
|
+
lines.splice(i, 1);
|
144
|
+
}
|
145
|
+
}
|
146
|
+
return lines
|
147
|
+
.filter(line => line.trim())
|
148
|
+
.map(line => ({
|
149
|
+
key: line.substr(0, line.indexOf(':')).trim().toLowerCase(),
|
150
|
+
line
|
151
|
+
}));
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
module.exports = MessageParser;
|
@@ -0,0 +1,154 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
// streams through a message body and calculates relaxed body hash
|
4
|
+
|
5
|
+
const Transform = require('stream').Transform;
|
6
|
+
const crypto = require('crypto');
|
7
|
+
|
8
|
+
class RelaxedBody extends Transform {
|
9
|
+
constructor(options) {
|
10
|
+
super();
|
11
|
+
options = options || {};
|
12
|
+
this.chunkBuffer = [];
|
13
|
+
this.chunkBufferLen = 0;
|
14
|
+
this.bodyHash = crypto.createHash(options.hashAlgo || 'sha1');
|
15
|
+
this.remainder = '';
|
16
|
+
this.byteLength = 0;
|
17
|
+
|
18
|
+
this.debug = options.debug;
|
19
|
+
this._debugBody = options.debug ? [] : false;
|
20
|
+
}
|
21
|
+
|
22
|
+
updateHash(chunk) {
|
23
|
+
let bodyStr;
|
24
|
+
|
25
|
+
// find next remainder
|
26
|
+
let nextRemainder = '';
|
27
|
+
|
28
|
+
// This crux finds and removes the spaces from the last line and the newline characters after the last non-empty line
|
29
|
+
// If we get another chunk that does not match this description then we can restore the previously processed data
|
30
|
+
let state = 'file';
|
31
|
+
for (let i = chunk.length - 1; i >= 0; i--) {
|
32
|
+
let c = chunk[i];
|
33
|
+
|
34
|
+
if (state === 'file' && (c === 0x0a || c === 0x0d)) {
|
35
|
+
// do nothing, found \n or \r at the end of chunk, stil end of file
|
36
|
+
} else if (state === 'file' && (c === 0x09 || c === 0x20)) {
|
37
|
+
// switch to line ending mode, this is the last non-empty line
|
38
|
+
state = 'line';
|
39
|
+
} else if (state === 'line' && (c === 0x09 || c === 0x20)) {
|
40
|
+
// do nothing, found ' ' or \t at the end of line, keep processing the last non-empty line
|
41
|
+
} else if (state === 'file' || state === 'line') {
|
42
|
+
// non line/file ending character found, switch to body mode
|
43
|
+
state = 'body';
|
44
|
+
if (i === chunk.length - 1) {
|
45
|
+
// final char is not part of line end or file end, so do nothing
|
46
|
+
break;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
if (i === 0) {
|
51
|
+
// reached to the beginning of the chunk, check if it is still about the ending
|
52
|
+
// and if the remainder also matches
|
53
|
+
if (
|
54
|
+
(state === 'file' && (!this.remainder || /[\r\n]$/.test(this.remainder))) ||
|
55
|
+
(state === 'line' && (!this.remainder || /[ \t]$/.test(this.remainder)))
|
56
|
+
) {
|
57
|
+
// keep everything
|
58
|
+
this.remainder += chunk.toString('binary');
|
59
|
+
return;
|
60
|
+
} else if (state === 'line' || state === 'file') {
|
61
|
+
// process existing remainder as normal line but store the current chunk
|
62
|
+
nextRemainder = chunk.toString('binary');
|
63
|
+
chunk = false;
|
64
|
+
break;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
if (state !== 'body') {
|
69
|
+
continue;
|
70
|
+
}
|
71
|
+
|
72
|
+
// reached first non ending byte
|
73
|
+
nextRemainder = chunk.slice(i + 1).toString('binary');
|
74
|
+
chunk = chunk.slice(0, i + 1);
|
75
|
+
break;
|
76
|
+
}
|
77
|
+
|
78
|
+
let needsFixing = !!this.remainder;
|
79
|
+
if (chunk && !needsFixing) {
|
80
|
+
// check if we even need to change anything
|
81
|
+
for (let i = 0, len = chunk.length; i < len; i++) {
|
82
|
+
if (i && chunk[i] === 0x0a && chunk[i - 1] !== 0x0d) {
|
83
|
+
// missing \r before \n
|
84
|
+
needsFixing = true;
|
85
|
+
break;
|
86
|
+
} else if (i && chunk[i] === 0x0d && chunk[i - 1] === 0x20) {
|
87
|
+
// trailing WSP found
|
88
|
+
needsFixing = true;
|
89
|
+
break;
|
90
|
+
} else if (i && chunk[i] === 0x20 && chunk[i - 1] === 0x20) {
|
91
|
+
// multiple spaces found, needs to be replaced with just one
|
92
|
+
needsFixing = true;
|
93
|
+
break;
|
94
|
+
} else if (chunk[i] === 0x09) {
|
95
|
+
// TAB found, needs to be replaced with a space
|
96
|
+
needsFixing = true;
|
97
|
+
break;
|
98
|
+
}
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
if (needsFixing) {
|
103
|
+
bodyStr = this.remainder + (chunk ? chunk.toString('binary') : '');
|
104
|
+
this.remainder = nextRemainder;
|
105
|
+
bodyStr = bodyStr
|
106
|
+
.replace(/\r?\n/g, '\n') // use js line endings
|
107
|
+
.replace(/[ \t]*$/gm, '') // remove line endings, rtrim
|
108
|
+
.replace(/[ \t]+/gm, ' ') // single spaces
|
109
|
+
.replace(/\n/g, '\r\n'); // restore rfc822 line endings
|
110
|
+
chunk = Buffer.from(bodyStr, 'binary');
|
111
|
+
} else if (nextRemainder) {
|
112
|
+
this.remainder = nextRemainder;
|
113
|
+
}
|
114
|
+
|
115
|
+
if (this.debug) {
|
116
|
+
this._debugBody.push(chunk);
|
117
|
+
}
|
118
|
+
this.bodyHash.update(chunk);
|
119
|
+
}
|
120
|
+
|
121
|
+
_transform(chunk, encoding, callback) {
|
122
|
+
if (!chunk || !chunk.length) {
|
123
|
+
return callback();
|
124
|
+
}
|
125
|
+
|
126
|
+
if (typeof chunk === 'string') {
|
127
|
+
chunk = Buffer.from(chunk, encoding);
|
128
|
+
}
|
129
|
+
|
130
|
+
this.updateHash(chunk);
|
131
|
+
|
132
|
+
this.byteLength += chunk.length;
|
133
|
+
this.push(chunk);
|
134
|
+
callback();
|
135
|
+
}
|
136
|
+
|
137
|
+
_flush(callback) {
|
138
|
+
// generate final hash and emit it
|
139
|
+
if (/[\r\n]$/.test(this.remainder) && this.byteLength > 2) {
|
140
|
+
// add terminating line end
|
141
|
+
this.bodyHash.update(Buffer.from('\r\n'));
|
142
|
+
}
|
143
|
+
if (!this.byteLength) {
|
144
|
+
// emit empty line buffer to keep the stream flowing
|
145
|
+
this.push(Buffer.from('\r\n'));
|
146
|
+
// this.bodyHash.update(Buffer.from('\r\n'));
|
147
|
+
}
|
148
|
+
|
149
|
+
this.emit('hash', this.bodyHash.digest('base64'), this.debug ? Buffer.concat(this._debugBody) : false);
|
150
|
+
callback();
|
151
|
+
}
|
152
|
+
}
|
153
|
+
|
154
|
+
module.exports = RelaxedBody;
|