@nocobase/plugin-workflow-mailer 2.3.0-beta.13 → 2.3.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/externalVersion.js +6 -6
- package/dist/node_modules/joi/package.json +1 -1
- package/dist/node_modules/nodemailer/dist/cjs/addressparser/index.d.ts +59 -0
- package/dist/node_modules/nodemailer/dist/cjs/addressparser/index.js +790 -0
- package/dist/node_modules/nodemailer/dist/cjs/base64/index.d.ts +36 -0
- package/dist/node_modules/nodemailer/dist/cjs/base64/index.js +119 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/index.d.ts +52 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/index.js +228 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/message-parser.d.ts +48 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/message-parser.js +180 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/relaxed-body.d.ts +36 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/relaxed-body.js +148 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/sign.d.ts +52 -0
- package/dist/node_modules/nodemailer/dist/cjs/dkim/sign.js +165 -0
- package/dist/node_modules/nodemailer/dist/cjs/errors.d.ts +96 -0
- package/dist/node_modules/nodemailer/dist/cjs/errors.js +70 -0
- package/dist/node_modules/nodemailer/dist/cjs/fetch/cookies.d.ts +107 -0
- package/dist/node_modules/nodemailer/dist/cjs/fetch/cookies.js +282 -0
- package/dist/node_modules/nodemailer/dist/cjs/fetch/index.d.ts +65 -0
- package/dist/node_modules/nodemailer/dist/cjs/fetch/index.js +384 -0
- package/dist/node_modules/nodemailer/dist/cjs/json-transport/index.d.ts +64 -0
- package/dist/node_modules/nodemailer/dist/cjs/json-transport/index.js +99 -0
- package/dist/node_modules/nodemailer/dist/cjs/mail-composer/index.d.ts +174 -0
- package/dist/node_modules/nodemailer/dist/cjs/mail-composer/index.js +613 -0
- package/dist/node_modules/nodemailer/dist/cjs/mailer/index.d.ts +237 -0
- package/dist/node_modules/nodemailer/dist/cjs/mailer/index.js +415 -0
- package/dist/node_modules/nodemailer/dist/cjs/mailer/mail-message.d.ts +97 -0
- package/dist/node_modules/nodemailer/dist/cjs/mailer/mail-message.js +342 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/index.d.ts +161 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/index.js +787 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/mime-types.d.ts +2 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-funcs/mime-types.js +2103 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/index.d.ts +333 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/index.js +1493 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/last-newline.d.ts +5 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/last-newline.js +32 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-unix.d.ts +9 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-unix.js +40 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-windows.d.ts +10 -0
- package/dist/node_modules/nodemailer/dist/cjs/mime-node/le-windows.js +47 -0
- package/dist/node_modules/nodemailer/dist/cjs/nodemailer.d.ts +124 -0
- package/dist/node_modules/nodemailer/dist/cjs/nodemailer.js +1 -0
- package/dist/node_modules/nodemailer/dist/cjs/package-info.d.ts +3 -0
- package/dist/node_modules/nodemailer/dist/cjs/package-info.js +7 -0
- package/dist/node_modules/nodemailer/dist/cjs/package.json +3 -0
- package/dist/node_modules/nodemailer/dist/cjs/punycode/index.d.ts +73 -0
- package/dist/node_modules/nodemailer/{lib → dist/cjs}/punycode/index.js +62 -111
- package/dist/node_modules/nodemailer/dist/cjs/qp/index.d.ts +32 -0
- package/dist/node_modules/nodemailer/dist/cjs/qp/index.js +207 -0
- package/dist/node_modules/nodemailer/dist/cjs/sendmail-transport/index.d.ts +69 -0
- package/dist/node_modules/nodemailer/dist/cjs/sendmail-transport/index.js +210 -0
- package/dist/node_modules/nodemailer/dist/cjs/ses-transport/index.d.ts +112 -0
- package/dist/node_modules/nodemailer/dist/cjs/ses-transport/index.js +252 -0
- package/dist/node_modules/nodemailer/dist/cjs/shared/index.d.ts +260 -0
- package/dist/node_modules/nodemailer/dist/cjs/shared/index.js +680 -0
- package/dist/node_modules/nodemailer/dist/cjs/shared/objects.d.ts +23 -0
- package/dist/node_modules/nodemailer/dist/cjs/shared/objects.js +43 -0
- package/dist/node_modules/nodemailer/dist/cjs/shared/url.d.ts +21 -0
- package/dist/node_modules/nodemailer/dist/cjs/shared/url.js +255 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/data-stream.d.ts +15 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/data-stream.js +108 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.d.ts +47 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/http-proxy-client.js +189 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/index.d.ts +416 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-connection/index.js +1837 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/index.d.ts +133 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/index.js +569 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/pool-resource.d.ts +62 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-pool/pool-resource.js +257 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-transport/index.d.ts +156 -0
- package/dist/node_modules/nodemailer/dist/cjs/smtp-transport/index.js +371 -0
- package/dist/node_modules/nodemailer/dist/cjs/stream-transport/index.d.ts +65 -0
- package/dist/node_modules/nodemailer/dist/cjs/stream-transport/index.js +146 -0
- package/dist/node_modules/nodemailer/dist/cjs/well-known/index.d.ts +35 -0
- package/dist/node_modules/nodemailer/dist/cjs/well-known/index.js +41 -0
- package/dist/node_modules/nodemailer/dist/cjs/well-known/services.d.ts +2 -0
- package/dist/node_modules/nodemailer/dist/cjs/well-known/services.js +674 -0
- package/dist/node_modules/nodemailer/dist/cjs/xoauth2/index.d.ts +201 -0
- package/dist/node_modules/nodemailer/dist/cjs/xoauth2/index.js +426 -0
- package/dist/node_modules/nodemailer/dist/esm/addressparser/index.d.ts +59 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/addressparser/index.js +361 -131
- package/dist/node_modules/nodemailer/dist/esm/base64/index.d.ts +36 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/base64/index.js +31 -56
- package/dist/node_modules/nodemailer/dist/esm/dkim/index.d.ts +52 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/dkim/index.js +49 -78
- package/dist/node_modules/nodemailer/dist/esm/dkim/message-parser.d.ts +48 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/dkim/message-parser.js +64 -42
- package/dist/node_modules/nodemailer/dist/esm/dkim/relaxed-body.d.ts +36 -0
- package/dist/node_modules/nodemailer/dist/esm/dkim/relaxed-body.js +141 -0
- package/dist/node_modules/nodemailer/dist/esm/dkim/sign.d.ts +52 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/dkim/sign.js +50 -46
- package/dist/node_modules/nodemailer/dist/esm/errors.d.ts +96 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/errors.js +26 -18
- package/dist/node_modules/nodemailer/dist/esm/fetch/cookies.d.ts +107 -0
- package/dist/node_modules/nodemailer/dist/esm/fetch/cookies.js +242 -0
- package/dist/node_modules/nodemailer/dist/esm/fetch/index.d.ts +65 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/fetch/index.js +80 -147
- package/dist/node_modules/nodemailer/dist/esm/json-transport/index.d.ts +64 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/json-transport/index.js +19 -39
- package/dist/node_modules/nodemailer/dist/esm/mail-composer/index.d.ts +174 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mail-composer/index.js +199 -265
- package/dist/node_modules/nodemailer/dist/esm/mailer/index.d.ts +237 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mailer/index.js +128 -234
- package/dist/node_modules/nodemailer/dist/esm/mailer/mail-message.d.ts +97 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mailer/mail-message.js +62 -114
- package/dist/node_modules/nodemailer/dist/esm/mime-funcs/index.d.ts +161 -0
- package/dist/node_modules/nodemailer/dist/esm/mime-funcs/index.js +738 -0
- package/dist/node_modules/nodemailer/dist/esm/mime-funcs/mime-types.d.ts +2 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-funcs/mime-types.js +29 -42
- package/dist/node_modules/nodemailer/dist/esm/mime-node/index.d.ts +333 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/index.js +259 -372
- package/dist/node_modules/nodemailer/dist/esm/mime-node/last-newline.d.ts +5 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/last-newline.js +4 -10
- package/dist/node_modules/nodemailer/dist/esm/mime-node/le-unix.d.ts +9 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/le-unix.js +6 -11
- package/dist/node_modules/nodemailer/dist/esm/mime-node/le-windows.d.ts +10 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/mime-node/le-windows.js +6 -13
- package/dist/node_modules/nodemailer/dist/esm/nodemailer.d.ts +124 -0
- package/dist/node_modules/nodemailer/dist/esm/nodemailer.js +166 -0
- package/dist/node_modules/nodemailer/dist/esm/package-info.d.ts +3 -0
- package/dist/node_modules/nodemailer/dist/esm/package-info.js +4 -0
- package/dist/node_modules/nodemailer/dist/esm/package.json +3 -0
- package/dist/node_modules/nodemailer/dist/esm/punycode/index.d.ts +73 -0
- package/dist/node_modules/nodemailer/dist/esm/punycode/index.js +405 -0
- package/dist/node_modules/nodemailer/dist/esm/qp/index.d.ts +32 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/qp/index.js +38 -67
- package/dist/node_modules/nodemailer/dist/esm/sendmail-transport/index.d.ts +69 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/sendmail-transport/index.js +51 -101
- package/dist/node_modules/nodemailer/dist/esm/ses-transport/index.d.ts +112 -0
- package/dist/node_modules/nodemailer/dist/esm/ses-transport/index.js +212 -0
- package/dist/node_modules/nodemailer/dist/esm/shared/index.d.ts +260 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/shared/index.js +156 -272
- package/dist/node_modules/nodemailer/dist/esm/shared/objects.d.ts +23 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/shared/objects.js +13 -17
- package/dist/node_modules/nodemailer/dist/esm/shared/url.d.ts +21 -0
- package/dist/node_modules/nodemailer/dist/esm/shared/url.js +214 -0
- package/dist/node_modules/nodemailer/dist/esm/smtp-connection/data-stream.d.ts +15 -0
- package/dist/node_modules/nodemailer/dist/esm/smtp-connection/data-stream.js +103 -0
- package/dist/node_modules/nodemailer/dist/esm/smtp-connection/http-proxy-client.d.ts +47 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-connection/http-proxy-client.js +49 -74
- package/dist/node_modules/nodemailer/dist/esm/smtp-connection/index.d.ts +416 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-connection/index.js +574 -794
- package/dist/node_modules/nodemailer/dist/esm/smtp-pool/index.d.ts +133 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-pool/index.js +172 -284
- package/dist/node_modules/nodemailer/dist/esm/smtp-pool/pool-resource.d.ts +62 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-pool/pool-resource.js +57 -97
- package/dist/node_modules/nodemailer/dist/esm/smtp-transport/index.d.ts +156 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/smtp-transport/index.js +81 -179
- package/dist/node_modules/nodemailer/dist/esm/stream-transport/index.d.ts +65 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/stream-transport/index.js +36 -74
- package/dist/node_modules/nodemailer/dist/esm/well-known/index.d.ts +35 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/well-known/index.js +5 -16
- package/dist/node_modules/nodemailer/dist/esm/well-known/services.d.ts +2 -0
- package/dist/node_modules/nodemailer/dist/esm/well-known/services.js +671 -0
- package/dist/node_modules/nodemailer/dist/esm/xoauth2/index.d.ts +201 -0
- package/dist/node_modules/nodemailer/{lib → dist/esm}/xoauth2/index.js +125 -187
- package/dist/node_modules/nodemailer/{lib → dist}/well-known/services.json +9 -2
- package/dist/node_modules/nodemailer/package.json +1 -1
- package/package.json +3 -3
- package/dist/node_modules/nodemailer/.gitattributes +0 -6
- package/dist/node_modules/nodemailer/.ncurc.js +0 -9
- package/dist/node_modules/nodemailer/.prettierignore +0 -8
- package/dist/node_modules/nodemailer/.prettierrc +0 -12
- package/dist/node_modules/nodemailer/.prettierrc.js +0 -10
- package/dist/node_modules/nodemailer/.release-please-config.json +0 -9
- package/dist/node_modules/nodemailer/eslint.config.js +0 -88
- package/dist/node_modules/nodemailer/lib/dkim/relaxed-body.js +0 -154
- package/dist/node_modules/nodemailer/lib/fetch/cookies.js +0 -276
- package/dist/node_modules/nodemailer/lib/mime-funcs/index.js +0 -671
- package/dist/node_modules/nodemailer/lib/nodemailer.js +0 -1
- package/dist/node_modules/nodemailer/lib/ses-transport/index.js +0 -266
- package/dist/node_modules/nodemailer/lib/shared/url.js +0 -151
- package/dist/node_modules/nodemailer/lib/smtp-connection/data-stream.js +0 -105
|
@@ -1,7 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { Transform } from 'node:stream';
|
|
2
|
+
/**
|
|
3
|
+
* Drops the SP and HTAB from both ends of a field name.
|
|
4
|
+
*
|
|
5
|
+
* An index scan rather than `/^[ \t]+|[ \t]+$/g`, which retries the trailing branch at
|
|
6
|
+
* every offset of a blank run that is followed by other text: the run is consumed greedily,
|
|
7
|
+
* `$` fails, and the engine gives the characters back one at a time before moving on to
|
|
8
|
+
* start the same walk one offset further in. A field name carrying a 128 KiB run of spaces
|
|
9
|
+
* between two atoms took about 6.7 seconds to trim, and the cost grows with its square.
|
|
10
|
+
*
|
|
11
|
+
* Not `.trim()`, which takes the other Unicode spaces along: an NBSP in front of a field
|
|
12
|
+
* name would come off and select the name into the signed set, where the relaxed
|
|
13
|
+
* canonicalization in sign.ts deliberately leaves everything but SP and HTAB alone.
|
|
14
|
+
*
|
|
15
|
+
* @param str Field name to trim
|
|
16
|
+
* @return The field name without the surrounding SP and HTAB
|
|
17
|
+
*/
|
|
18
|
+
function _trimFieldName(str) {
|
|
19
|
+
let start = 0;
|
|
20
|
+
let end = str.length;
|
|
21
|
+
while (start < end && (str.charCodeAt(start) === 0x20 || str.charCodeAt(start) === 0x09)) {
|
|
22
|
+
start++;
|
|
23
|
+
}
|
|
24
|
+
while (end > start && (str.charCodeAt(end - 1) === 0x20 || str.charCodeAt(end - 1) === 0x09)) {
|
|
25
|
+
end--;
|
|
26
|
+
}
|
|
27
|
+
return str.slice(start, end);
|
|
28
|
+
}
|
|
5
29
|
/**
|
|
6
30
|
* MessageParser instance is a transform stream that separates message headers
|
|
7
31
|
* from the rest of the body. Headers are emitted with the 'headers' event. Message
|
|
@@ -17,46 +41,42 @@ class MessageParser extends Transform {
|
|
|
17
41
|
this.rawHeaders = false;
|
|
18
42
|
this.bodySize = 0;
|
|
19
43
|
}
|
|
20
|
-
|
|
21
44
|
/**
|
|
22
45
|
* Keeps count of the last 4 bytes in order to detect line breaks on chunk boundaries
|
|
23
46
|
*
|
|
24
|
-
* @param
|
|
47
|
+
* @param data Next data chunk from the stream
|
|
25
48
|
*/
|
|
26
49
|
updateLastBytes(data) {
|
|
27
50
|
const lblen = this.lastBytes.length;
|
|
28
51
|
const nblen = Math.min(data.length, lblen);
|
|
29
|
-
|
|
30
52
|
// shift existing bytes
|
|
31
53
|
for (let i = 0, len = lblen - nblen; i < len; i++) {
|
|
32
54
|
this.lastBytes[i] = this.lastBytes[i + nblen];
|
|
33
55
|
}
|
|
34
|
-
|
|
35
56
|
// add new bytes
|
|
36
57
|
for (let i = 1; i <= nblen; i++) {
|
|
37
58
|
this.lastBytes[lblen - i] = data[data.length - i];
|
|
38
59
|
}
|
|
39
60
|
}
|
|
40
|
-
|
|
41
61
|
/**
|
|
42
62
|
* Finds and removes message headers from the remaining body. We want to keep
|
|
43
63
|
* headers separated until final delivery to be able to modify these
|
|
44
64
|
*
|
|
45
|
-
* @param
|
|
46
|
-
* @return
|
|
65
|
+
* @param data Next chunk of data
|
|
66
|
+
* @return Returns true if headers are already found or false otherwise
|
|
47
67
|
*/
|
|
48
68
|
checkHeaders(data) {
|
|
49
69
|
if (this.headersParsed) {
|
|
50
70
|
return true;
|
|
51
71
|
}
|
|
52
|
-
|
|
53
72
|
const lblen = this.lastBytes.length;
|
|
54
73
|
let headerPos = 0;
|
|
55
74
|
for (let i = 0, len = this.lastBytes.length + data.length; i < len; i++) {
|
|
56
75
|
let chr;
|
|
57
76
|
if (i < lblen) {
|
|
58
77
|
chr = this.lastBytes[i];
|
|
59
|
-
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
60
80
|
chr = data[i - lblen];
|
|
61
81
|
}
|
|
62
82
|
if (chr === 0x0a && i) {
|
|
@@ -67,7 +87,8 @@ class MessageParser extends Transform {
|
|
|
67
87
|
headerPos = i - lblen + 1;
|
|
68
88
|
this.headerBytes += headerPos;
|
|
69
89
|
break;
|
|
70
|
-
}
|
|
90
|
+
}
|
|
91
|
+
else if (pr1 === 0x0d && pr2 === 0x0a) {
|
|
71
92
|
this.headersParsed = true;
|
|
72
93
|
headerPos = i - lblen + 1;
|
|
73
94
|
this.headerBytes += headerPos;
|
|
@@ -75,13 +96,12 @@ class MessageParser extends Transform {
|
|
|
75
96
|
}
|
|
76
97
|
}
|
|
77
98
|
}
|
|
78
|
-
|
|
79
99
|
if (this.headersParsed) {
|
|
80
100
|
this.headerChunks.push(data.slice(0, headerPos));
|
|
81
101
|
this.rawHeaders = Buffer.concat(this.headerChunks, this.headerBytes);
|
|
82
102
|
this.headerChunks = null;
|
|
83
103
|
this.emit('headers', this.parseHeaders());
|
|
84
|
-
if (data.length
|
|
104
|
+
if (data.length > headerPos) {
|
|
85
105
|
const chunk = data.slice(headerPos);
|
|
86
106
|
this.bodySize += chunk.length;
|
|
87
107
|
// this would be the first chunk of data sent downstream
|
|
@@ -89,66 +109,68 @@ class MessageParser extends Transform {
|
|
|
89
109
|
}
|
|
90
110
|
return false;
|
|
91
111
|
}
|
|
92
|
-
|
|
93
112
|
this.headerBytes += data.length;
|
|
94
113
|
this.headerChunks.push(data);
|
|
95
|
-
|
|
96
114
|
// store last 4 bytes to catch header break
|
|
97
115
|
this.updateLastBytes(data);
|
|
98
|
-
|
|
99
116
|
return false;
|
|
100
117
|
}
|
|
101
|
-
|
|
118
|
+
/** @internal */
|
|
102
119
|
_transform(chunk, encoding, callback) {
|
|
103
120
|
if (!chunk || !chunk.length) {
|
|
104
121
|
return callback();
|
|
105
122
|
}
|
|
106
|
-
|
|
107
123
|
if (typeof chunk === 'string') {
|
|
108
124
|
chunk = Buffer.from(chunk, encoding);
|
|
109
125
|
}
|
|
110
|
-
|
|
111
126
|
let headersFound;
|
|
112
|
-
|
|
113
127
|
try {
|
|
114
128
|
headersFound = this.checkHeaders(chunk);
|
|
115
|
-
}
|
|
129
|
+
}
|
|
130
|
+
catch (E) {
|
|
116
131
|
return callback(E);
|
|
117
132
|
}
|
|
118
|
-
|
|
119
133
|
if (headersFound) {
|
|
120
134
|
this.bodySize += chunk.length;
|
|
121
135
|
this.push(chunk);
|
|
122
136
|
}
|
|
123
|
-
|
|
124
137
|
setImmediate(callback);
|
|
125
138
|
}
|
|
126
|
-
|
|
139
|
+
/** @internal */
|
|
127
140
|
_flush(callback) {
|
|
128
141
|
if (this.headerChunks) {
|
|
129
|
-
|
|
130
|
-
this.
|
|
131
|
-
this.push(chunk);
|
|
142
|
+
// no empty line was seen, so the message consists of headers only
|
|
143
|
+
this.rawHeaders = Buffer.concat(this.headerChunks, this.headerBytes);
|
|
132
144
|
this.headerChunks = null;
|
|
145
|
+
this.emit('headers', this.parseHeaders());
|
|
133
146
|
}
|
|
134
147
|
callback();
|
|
135
148
|
}
|
|
136
|
-
|
|
137
149
|
parseHeaders() {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
150
|
+
// the header bytes are kept as they are, one character per byte, so the
|
|
151
|
+
// signature covers exactly the bytes the receiving side canonicalizes
|
|
152
|
+
// Only SP and HTAB fold a line, and only they are trimmed from the field name, the
|
|
153
|
+
// same whitespace the relaxed canonicalization in sign.ts works with
|
|
154
|
+
const rawLines = (this.rawHeaders || Buffer.alloc(0)).toString('binary').split(/\r?\n/);
|
|
155
|
+
// Unfold in a single forward pass and only ever test a freshly split line for the
|
|
156
|
+
// continuation prefix. Testing an already merged line instead would rescan a string
|
|
157
|
+
// that grows with every continuation line, so a header folded into many continuation
|
|
158
|
+
// lines (a large recipient list, for example) would take quadratic time to unfold
|
|
159
|
+
const lines = [];
|
|
160
|
+
for (const rawLine of rawLines) {
|
|
161
|
+
if (lines.length && /^[ \t]/.test(rawLine)) {
|
|
162
|
+
lines[lines.length - 1] += '\n' + rawLine;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
lines.push(rawLine);
|
|
143
166
|
}
|
|
144
167
|
}
|
|
145
168
|
return lines
|
|
146
|
-
.filter(line => line
|
|
169
|
+
.filter(line => /[^ \t\r]/.test(line))
|
|
147
170
|
.map(line => ({
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
171
|
+
key: _trimFieldName(line.substr(0, line.indexOf(':'))).toLowerCase(),
|
|
172
|
+
line
|
|
173
|
+
}));
|
|
151
174
|
}
|
|
152
175
|
}
|
|
153
|
-
|
|
154
|
-
module.exports = MessageParser;
|
|
176
|
+
export default MessageParser;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Transform } from 'node:stream';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
/**
|
|
4
|
+
* Options for the relaxed body hash stream
|
|
5
|
+
*/
|
|
6
|
+
export interface RelaxedBodyOptions {
|
|
7
|
+
/** Hash algorithm for the body hash, defaults to sha256 */
|
|
8
|
+
hashAlgo?: string | undefined;
|
|
9
|
+
/** Collect the canonicalized body and emit it with the 'hash' event */
|
|
10
|
+
debug?: boolean | undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Passes the message body through unchanged and hashes its relaxed
|
|
14
|
+
* canonicalization (RFC 6376 section 3.4.4) on the side: whitespace at the end
|
|
15
|
+
* of a line is dropped, runs of whitespace within a line become a single space,
|
|
16
|
+
* every line ends with CRLF, empty lines at the end of the body are ignored and
|
|
17
|
+
* a non-empty body always ends with CRLF. Bytes are canonicalized as they arrive,
|
|
18
|
+
* so a line of any length costs constant memory.
|
|
19
|
+
*/
|
|
20
|
+
declare class RelaxedBody extends Transform {
|
|
21
|
+
bodyHash: crypto.Hash;
|
|
22
|
+
/** Bytes of the original body seen so far */
|
|
23
|
+
byteLength: number;
|
|
24
|
+
debug: boolean | undefined;
|
|
25
|
+
constructor(options?: RelaxedBodyOptions);
|
|
26
|
+
updateHash(chunk: Buffer, final?: boolean): void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Type aliases in the layout of @types/nodemailer, so `RelaxedBody.Options` style references keep working
|
|
30
|
+
*/
|
|
31
|
+
declare namespace RelaxedBody {
|
|
32
|
+
type Options = RelaxedBodyOptions;
|
|
33
|
+
}
|
|
34
|
+
/** The same alias as a module level export, for `import * as RelaxedBody` and `import RelaxedBody = require()` */
|
|
35
|
+
export type { RelaxedBodyOptions as Options };
|
|
36
|
+
export default RelaxedBody;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// streams through a message body and calculates relaxed body hash
|
|
2
|
+
import { Transform } from 'node:stream';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
const CHAR_CR = 0x0d;
|
|
5
|
+
const CHAR_LF = 0x0a;
|
|
6
|
+
const CHAR_SPACE = 0x20;
|
|
7
|
+
const CHAR_TAB = 0x09;
|
|
8
|
+
const CRLF = Buffer.from('\r\n');
|
|
9
|
+
// a run of empty lines is hashed from this buffer in slices
|
|
10
|
+
const EMPTY_LINES = Buffer.alloc(4096, CRLF);
|
|
11
|
+
/**
|
|
12
|
+
* Passes the message body through unchanged and hashes its relaxed
|
|
13
|
+
* canonicalization (RFC 6376 section 3.4.4) on the side: whitespace at the end
|
|
14
|
+
* of a line is dropped, runs of whitespace within a line become a single space,
|
|
15
|
+
* every line ends with CRLF, empty lines at the end of the body are ignored and
|
|
16
|
+
* a non-empty body always ends with CRLF. Bytes are canonicalized as they arrive,
|
|
17
|
+
* so a line of any length costs constant memory.
|
|
18
|
+
*/
|
|
19
|
+
class RelaxedBody extends Transform {
|
|
20
|
+
constructor(options) {
|
|
21
|
+
super();
|
|
22
|
+
options = options || {};
|
|
23
|
+
this.bodyHash = crypto.createHash(options.hashAlgo || 'sha256');
|
|
24
|
+
this.byteLength = 0;
|
|
25
|
+
this.debug = options.debug;
|
|
26
|
+
this._debugBody = options.debug ? [] : false;
|
|
27
|
+
this._lineHasContent = false;
|
|
28
|
+
this._pendingWsp = false;
|
|
29
|
+
this._pendingCr = false;
|
|
30
|
+
this._pendingEmptyLines = 0;
|
|
31
|
+
}
|
|
32
|
+
/** @internal */
|
|
33
|
+
_hashCanonical(data) {
|
|
34
|
+
if (!data.length) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.bodyHash.update(data);
|
|
38
|
+
if (this._debugBody) {
|
|
39
|
+
this._debugBody.push(Buffer.from(data));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** @internal */
|
|
43
|
+
_hashEmptyLines() {
|
|
44
|
+
while (this._pendingEmptyLines > 0) {
|
|
45
|
+
const count = Math.min(this._pendingEmptyLines, EMPTY_LINES.length / 2);
|
|
46
|
+
this._hashCanonical(EMPTY_LINES.subarray(0, count * 2));
|
|
47
|
+
this._pendingEmptyLines -= count;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Writes a content byte, with the space a pending run of whitespace collapses to,
|
|
52
|
+
* into the output buffer and returns the new write position. Kept a method rather
|
|
53
|
+
* than a closure so the write position stays a plain local in the byte loop
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
_emitContent(out, outPos, c) {
|
|
57
|
+
if (!this._lineHasContent) {
|
|
58
|
+
if (this._pendingEmptyLines) {
|
|
59
|
+
// the first content byte of a line is where the empty lines before it
|
|
60
|
+
// become part of the body, so hash what is in the buffer before them
|
|
61
|
+
this._hashCanonical(out.subarray(0, outPos));
|
|
62
|
+
outPos = 0;
|
|
63
|
+
this._hashEmptyLines();
|
|
64
|
+
}
|
|
65
|
+
this._lineHasContent = true;
|
|
66
|
+
}
|
|
67
|
+
if (this._pendingWsp) {
|
|
68
|
+
out[outPos++] = CHAR_SPACE;
|
|
69
|
+
this._pendingWsp = false;
|
|
70
|
+
}
|
|
71
|
+
out[outPos++] = c;
|
|
72
|
+
return outPos;
|
|
73
|
+
}
|
|
74
|
+
updateHash(chunk, final) {
|
|
75
|
+
// every byte contributes itself at most once, plus a CR for a bare LF
|
|
76
|
+
// and, once per chunk, a pending space and CR carried over from before
|
|
77
|
+
const out = Buffer.allocUnsafe(chunk.length * 2 + 2);
|
|
78
|
+
let outPos = 0;
|
|
79
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
80
|
+
const c = chunk[i];
|
|
81
|
+
if (c === CHAR_LF) {
|
|
82
|
+
// end of line, a CR right before it and any trailing whitespace are dropped
|
|
83
|
+
if (this._lineHasContent) {
|
|
84
|
+
out[outPos++] = CHAR_CR;
|
|
85
|
+
out[outPos++] = CHAR_LF;
|
|
86
|
+
this._lineHasContent = false;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
this._pendingEmptyLines++;
|
|
90
|
+
}
|
|
91
|
+
this._pendingWsp = false;
|
|
92
|
+
this._pendingCr = false;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (this._pendingCr) {
|
|
96
|
+
// not followed by LF, so the CR is content
|
|
97
|
+
outPos = this._emitContent(out, outPos, CHAR_CR);
|
|
98
|
+
this._pendingCr = false;
|
|
99
|
+
}
|
|
100
|
+
if (c === CHAR_CR) {
|
|
101
|
+
this._pendingCr = true;
|
|
102
|
+
}
|
|
103
|
+
else if (c === CHAR_SPACE || c === CHAR_TAB) {
|
|
104
|
+
this._pendingWsp = true;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
outPos = this._emitContent(out, outPos, c);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (final && this._pendingCr) {
|
|
111
|
+
// a CR at the very end of the body is content
|
|
112
|
+
outPos = this._emitContent(out, outPos, CHAR_CR);
|
|
113
|
+
this._pendingCr = false;
|
|
114
|
+
}
|
|
115
|
+
this._hashCanonical(out.subarray(0, outPos));
|
|
116
|
+
}
|
|
117
|
+
/** @internal */
|
|
118
|
+
_transform(chunk, encoding, callback) {
|
|
119
|
+
if (!chunk || !chunk.length) {
|
|
120
|
+
return callback();
|
|
121
|
+
}
|
|
122
|
+
if (typeof chunk === 'string') {
|
|
123
|
+
chunk = Buffer.from(chunk, encoding);
|
|
124
|
+
}
|
|
125
|
+
this.updateHash(chunk);
|
|
126
|
+
this.byteLength += chunk.length;
|
|
127
|
+
this.push(chunk);
|
|
128
|
+
callback();
|
|
129
|
+
}
|
|
130
|
+
/** @internal */
|
|
131
|
+
_flush(callback) {
|
|
132
|
+
this.updateHash(Buffer.alloc(0), true);
|
|
133
|
+
if (this._lineHasContent) {
|
|
134
|
+
// the body does not end with a line break, add one
|
|
135
|
+
this._hashCanonical(CRLF);
|
|
136
|
+
}
|
|
137
|
+
this.emit('hash', this.bodyHash.digest('base64'), this.debug ? Buffer.concat(this._debugBody) : false);
|
|
138
|
+
callback();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export default RelaxedBody;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import type { NodemailerError } from '../errors.js';
|
|
3
|
+
import type { MessageParserHeaderLine } from './message-parser.js';
|
|
4
|
+
/**
|
|
5
|
+
* Private key accepted by crypto.Sign#sign: a PEM string, a Buffer, a KeyObject or an
|
|
6
|
+
* object with the key and its passphrase
|
|
7
|
+
*/
|
|
8
|
+
export type DKIMPrivateKey = crypto.KeyLike | crypto.SignKeyObjectInput | crypto.SignPrivateKeyInput;
|
|
9
|
+
/**
|
|
10
|
+
* Options for the DKIM signature header generator
|
|
11
|
+
*/
|
|
12
|
+
export interface DKIMKey {
|
|
13
|
+
/** Domain name to be signed for */
|
|
14
|
+
domainName?: string | undefined;
|
|
15
|
+
/** DKIM key selector to use */
|
|
16
|
+
keySelector?: string | undefined;
|
|
17
|
+
/** DKIM private key to use */
|
|
18
|
+
privateKey?: DKIMPrivateKey | undefined;
|
|
19
|
+
}
|
|
20
|
+
export interface DKIMSignOptions extends DKIMKey {
|
|
21
|
+
/** Colon separated list of header field names to sign, defaults to the RFC4871 list */
|
|
22
|
+
headerFieldNames?: string | undefined;
|
|
23
|
+
/** Colon separated list of header field names to leave out of the signature */
|
|
24
|
+
skipFields?: string | undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Canonicalized headers and the list of field names that went into them
|
|
28
|
+
*/
|
|
29
|
+
export interface DKIMRelaxedHeaders {
|
|
30
|
+
/** Relaxed header lines, each terminated with CRLF, one character per byte ('binary' encoding) */
|
|
31
|
+
headers: string;
|
|
32
|
+
/** Colon separated list of the field names that were included */
|
|
33
|
+
fieldNames: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Returns DKIM signature header line
|
|
37
|
+
*
|
|
38
|
+
* @param headers Parsed headers object from MessageParser
|
|
39
|
+
* @param hashAlgo Hash algorithm the body hash was calculated with, for example "sha256"
|
|
40
|
+
* @param bodyHash Base64 encoded hash of the message
|
|
41
|
+
* @param options DKIM options
|
|
42
|
+
* @param options.domainName Domain name to be signed for
|
|
43
|
+
* @param options.keySelector DKIM key selector to use
|
|
44
|
+
* @param options.privateKey DKIM private key to use
|
|
45
|
+
* @return Complete header line
|
|
46
|
+
*/
|
|
47
|
+
declare function sign(headers: MessageParserHeaderLine[], hashAlgo: string, bodyHash: string, options?: DKIMSignOptions): string | false;
|
|
48
|
+
declare namespace sign {
|
|
49
|
+
var relaxedHeaders: (headers: MessageParserHeaderLine[], fieldNames?: string, skipFields?: string) => DKIMRelaxedHeaders;
|
|
50
|
+
var unsupportedHashAlgoError: (hashAlgo: string) => NodemailerError;
|
|
51
|
+
}
|
|
52
|
+
export default sign;
|
|
@@ -1,60 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import * as punycode from '../punycode/index.js';
|
|
2
|
+
import * as mimeFuncs from '../mime-funcs/index.js';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import * as errors from '../errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* Error for a hashAlgo value the crypto module does not know
|
|
7
|
+
*/
|
|
8
|
+
function unsupportedHashAlgoError(hashAlgo) {
|
|
9
|
+
const err = new Error('Unsupported DKIM hash algorithm "' + hashAlgo + '"');
|
|
10
|
+
err.code = errors.ECONFIG;
|
|
11
|
+
return err;
|
|
12
|
+
}
|
|
7
13
|
/**
|
|
8
14
|
* Returns DKIM signature header line
|
|
9
15
|
*
|
|
10
|
-
* @param
|
|
11
|
-
* @param
|
|
12
|
-
* @param
|
|
13
|
-
* @param
|
|
14
|
-
* @param
|
|
15
|
-
* @param
|
|
16
|
-
* @
|
|
16
|
+
* @param headers Parsed headers object from MessageParser
|
|
17
|
+
* @param hashAlgo Hash algorithm the body hash was calculated with, for example "sha256"
|
|
18
|
+
* @param bodyHash Base64 encoded hash of the message
|
|
19
|
+
* @param options DKIM options
|
|
20
|
+
* @param options.domainName Domain name to be signed for
|
|
21
|
+
* @param options.keySelector DKIM key selector to use
|
|
22
|
+
* @param options.privateKey DKIM private key to use
|
|
23
|
+
* @return Complete header line
|
|
17
24
|
*/
|
|
18
|
-
|
|
19
|
-
module.exports = (headers, hashAlgo, bodyHash, options) => {
|
|
25
|
+
function sign(headers, hashAlgo, bodyHash, options) {
|
|
20
26
|
options = options || {};
|
|
21
|
-
|
|
22
27
|
// all listed fields from RFC4871 #5.5
|
|
23
|
-
const defaultFieldNames =
|
|
24
|
-
'From:Sender:Reply-To:Subject:Date:Message-ID:To:' +
|
|
28
|
+
const defaultFieldNames = 'From:Sender:Reply-To:Subject:Date:Message-ID:To:' +
|
|
25
29
|
'Cc:MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID:' +
|
|
26
30
|
'Content-Description:Resent-Date:Resent-From:Resent-Sender:' +
|
|
27
31
|
'Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:' +
|
|
28
32
|
'List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:' +
|
|
29
33
|
'List-Owner:List-Archive';
|
|
30
|
-
|
|
31
34
|
const fieldNames = options.headerFieldNames || defaultFieldNames;
|
|
32
|
-
|
|
33
35
|
const canonicalizedHeaderData = relaxedHeaders(headers, fieldNames, options.skipFields);
|
|
34
36
|
const dkimHeader = generateDKIMHeader(options.domainName, options.keySelector, canonicalizedHeaderData.fieldNames, hashAlgo, bodyHash);
|
|
35
|
-
|
|
36
37
|
canonicalizedHeaderData.headers += 'dkim-signature:' + relaxedHeaderLine(dkimHeader);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
let signer;
|
|
39
|
+
try {
|
|
40
|
+
signer = crypto.createSign(('rsa-' + hashAlgo).toUpperCase());
|
|
41
|
+
}
|
|
42
|
+
catch (_E) {
|
|
43
|
+
throw unsupportedHashAlgoError(hashAlgo);
|
|
44
|
+
}
|
|
45
|
+
// the header lines are 'binary' strings, so this reproduces the original header bytes
|
|
46
|
+
signer.update(canonicalizedHeaderData.headers, 'latin1');
|
|
40
47
|
let signature;
|
|
41
48
|
try {
|
|
42
49
|
signature = signer.sign(options.privateKey, 'base64');
|
|
43
|
-
}
|
|
50
|
+
}
|
|
51
|
+
catch (_E) {
|
|
44
52
|
return false;
|
|
45
53
|
}
|
|
46
|
-
|
|
47
54
|
return dkimHeader + signature.replace(/(^.{73}|.{75}(?!\r?\n|\r))/g, '$&\r\n ').trim();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
}
|
|
56
|
+
sign.relaxedHeaders = relaxedHeaders;
|
|
57
|
+
sign.unsupportedHashAlgoError = unsupportedHashAlgoError;
|
|
58
|
+
export default sign;
|
|
52
59
|
function generateDKIMHeader(domainName, keySelector, fieldNames, hashAlgo, bodyHash) {
|
|
53
60
|
// the caller supplied tag values are interpolated straight into the tag list, and none of
|
|
54
61
|
// them has any way to carry a control char, DEL, or one of the delimiters that would close
|
|
55
62
|
// the value and open a tag of its own
|
|
56
|
-
const cleanTagValue = value => (value || '').toString().replace(/[\x00-\x1f\x7f;=]/g, '');
|
|
57
|
-
|
|
63
|
+
const cleanTagValue = (value) => (value || '').toString().replace(/[\x00-\x1f\x7f;=]/g, '');
|
|
58
64
|
const dkim = [
|
|
59
65
|
'v=1',
|
|
60
66
|
'a=rsa-' + hashAlgo,
|
|
@@ -65,30 +71,25 @@ function generateDKIMHeader(domainName, keySelector, fieldNames, hashAlgo, bodyH
|
|
|
65
71
|
'bh=' + bodyHash,
|
|
66
72
|
'h=' + cleanTagValue(fieldNames)
|
|
67
73
|
].join('; ');
|
|
68
|
-
|
|
69
74
|
return mimeFuncs.foldLines('DKIM-Signature: ' + dkim, 76) + ';\r\n b=';
|
|
70
75
|
}
|
|
71
|
-
|
|
72
76
|
function relaxedHeaders(headers, fieldNames, skipFields) {
|
|
73
77
|
const includedFields = new Set();
|
|
74
78
|
const skip = new Set();
|
|
75
79
|
const headerFields = new Map();
|
|
76
|
-
|
|
77
80
|
(skipFields || '')
|
|
78
81
|
.toLowerCase()
|
|
79
82
|
.split(':')
|
|
80
83
|
.forEach(field => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
skip.add(field.trim());
|
|
85
|
+
});
|
|
84
86
|
(fieldNames || '')
|
|
85
87
|
.toLowerCase()
|
|
86
88
|
.split(':')
|
|
87
89
|
.filter(field => !skip.has(field.trim()))
|
|
88
90
|
.forEach(field => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
includedFields.add(field.trim());
|
|
92
|
+
});
|
|
92
93
|
for (let i = headers.length - 1; i >= 0; i--) {
|
|
93
94
|
const line = headers[i];
|
|
94
95
|
// only include the first value from bottom to top
|
|
@@ -96,7 +97,6 @@ function relaxedHeaders(headers, fieldNames, skipFields) {
|
|
|
96
97
|
headerFields.set(line.key, relaxedHeaderLine(line.line));
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
|
-
|
|
100
100
|
const headersList = [];
|
|
101
101
|
const fields = [];
|
|
102
102
|
includedFields.forEach(field => {
|
|
@@ -105,17 +105,21 @@ function relaxedHeaders(headers, fieldNames, skipFields) {
|
|
|
105
105
|
headersList.push(field + ':' + headerFields.get(field));
|
|
106
106
|
}
|
|
107
107
|
});
|
|
108
|
-
|
|
109
108
|
return {
|
|
110
109
|
headers: headersList.join('\r\n') + '\r\n',
|
|
111
110
|
fieldNames: fields.join(':')
|
|
112
111
|
};
|
|
113
112
|
}
|
|
114
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Relaxed canonicalization of a header field value (RFC 6376 section 3.4.2): unfold, turn
|
|
115
|
+
* every run of SP and HTAB into a single SP and drop the whitespace next to the colon and
|
|
116
|
+
* at the end. Only SP and HTAB count as whitespace, so bytes that decode to other space
|
|
117
|
+
* characters, such as a non-breaking space in a UTF-8 header, stay as they are
|
|
118
|
+
*/
|
|
115
119
|
function relaxedHeaderLine(line) {
|
|
116
120
|
return line
|
|
117
121
|
.substr(line.indexOf(':') + 1)
|
|
118
122
|
.replace(/\r?\n/g, '')
|
|
119
|
-
.replace(
|
|
120
|
-
.
|
|
123
|
+
.replace(/[ \t]+/g, ' ')
|
|
124
|
+
.replace(/^ | $/g, '');
|
|
121
125
|
}
|