source: node_modules/nodemailer/lib/dkim/relaxed-body.js@ 81bc7da

finki-main main
Last change on this file since 81bc7da was 81bc7da, checked in by Klimentina Efremova <klimentina08642@…>, 3 months ago

Initial commit

  • Property mode set to 100644
File size: 5.6 KB
Line 
1'use strict';
2
3// streams through a message body and calculates relaxed body hash
4
5const Transform = require('stream').Transform;
6const crypto = require('crypto');
7
8class 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
154module.exports = RelaxedBody;
Note: See TracBrowser for help on using the repository browser.