@blamejs/core 0.8.52 → 0.8.57
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/CHANGELOG.md +5 -0
- package/index.js +8 -0
- package/lib/audit.js +4 -0
- package/lib/auth/fido-mds3.js +624 -0
- package/lib/auth/passkey.js +214 -2
- package/lib/auth-bot-challenge.js +1 -1
- package/lib/credential-hash.js +2 -2
- package/lib/framework-error.js +55 -0
- package/lib/guard-cidr.js +2 -1
- package/lib/guard-jwt.js +2 -2
- package/lib/guard-oauth.js +2 -2
- package/lib/http-client-cache.js +916 -0
- package/lib/http-client.js +242 -0
- package/lib/mail-arf.js +343 -0
- package/lib/mail-auth.js +265 -40
- package/lib/mail-bimi.js +948 -33
- package/lib/mail-bounce.js +386 -4
- package/lib/mail-mdn.js +424 -0
- package/lib/mail-unsubscribe.js +265 -25
- package/lib/mail.js +403 -21
- package/lib/middleware/bearer-auth.js +1 -1
- package/lib/middleware/clear-site-data.js +122 -0
- package/lib/middleware/dpop.js +1 -1
- package/lib/middleware/index.js +9 -0
- package/lib/middleware/nel.js +214 -0
- package/lib/middleware/security-headers.js +56 -4
- package/lib/middleware/speculation-rules.js +323 -0
- package/lib/mime-parse.js +198 -0
- package/lib/network-dns.js +890 -27
- package/lib/network-tls.js +745 -0
- package/lib/object-store/sigv4.js +54 -0
- package/lib/public-suffix.js +414 -0
- package/lib/safe-buffer.js +7 -0
- package/lib/safe-json.js +1 -1
- package/lib/static.js +120 -0
- package/lib/storage.js +11 -0
- package/lib/vendor/MANIFEST.json +33 -0
- package/lib/vendor/bimi-trust-anchors.pem +33 -0
- package/lib/vendor/public-suffix-list.dat +16376 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/lib/mail-unsubscribe.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* mail-unsubscribe — RFC 8058 / RFC 2369 List
|
|
3
|
+
* mail-unsubscribe — RFC 8058 / RFC 2369 / RFC 2919 List-* support.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Three pieces:
|
|
6
6
|
* 1. buildHeaders({ url, mailto, oneClick }) — produces the
|
|
7
7
|
* `List-Unsubscribe` and (when oneClick) `List-Unsubscribe-Post`
|
|
8
8
|
* header values that get merged into the outbound message.
|
|
9
|
-
* 2.
|
|
9
|
+
* 2. buildAllListHeaders({ unsubscribeUrl, helpUrl, ownerEmail,
|
|
10
|
+
* archiveUrl, listId, listOwner }) — single-call builder for the
|
|
11
|
+
* full RFC 2369 / RFC 2919 List-* header bundle (List-Unsubscribe,
|
|
12
|
+
* List-Help, List-Archive, List-Owner, List-Post, List-ID).
|
|
13
|
+
* Every URL/email/list-id is shape-validated at config-time so
|
|
14
|
+
* operator typos surface here, not silently downstream.
|
|
15
|
+
* 3. handler({ onUnsubscribe }) — request-lifecycle middleware that
|
|
10
16
|
* validates the RFC 8058 one-click POST body
|
|
11
17
|
* (`List-Unsubscribe=One-Click`) and dispatches to the operator's
|
|
12
18
|
* onUnsubscribe callback. Returns 200 OK with empty body on
|
|
@@ -39,41 +45,164 @@
|
|
|
39
45
|
* app.post("/email/unsubscribe", unsubMw);
|
|
40
46
|
*/
|
|
41
47
|
|
|
48
|
+
var C = require("./constants");
|
|
42
49
|
var lazyRequire = require("./lazy-require");
|
|
43
50
|
var safeUrl = require("./safe-url");
|
|
51
|
+
var validateOpts = require("./validate-opts");
|
|
52
|
+
var { MailUnsubscribeError } = require("./framework-error");
|
|
44
53
|
|
|
45
54
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
46
55
|
void observability;
|
|
47
56
|
|
|
57
|
+
// RFC 5322 / 5321 header-value upper bound. Used to refuse hostile
|
|
58
|
+
// over-length operator inputs at config-time (throw at config-time).
|
|
59
|
+
var HEADER_VALUE_MAX_BYTES = C.BYTES.kib(2);
|
|
60
|
+
|
|
61
|
+
// RFC 2919 §3 List-ID shape: a phrase (optional) followed by an
|
|
62
|
+
// angle-addr containing a label-list (one or more dot-separated
|
|
63
|
+
// labels). We accept either the raw label-list form
|
|
64
|
+
// `lst.example.com` (most common shape — bare-form opt-in) OR the
|
|
65
|
+
// full `Phrase <lst.example.com>` form. Refuse shapes that smuggle
|
|
66
|
+
// arbitrary header bytes.
|
|
67
|
+
//
|
|
68
|
+
// Label LDH check is inlined as a charCode loop instead of the
|
|
69
|
+
// canonical hostname regex shape — list-id labels are syntactically
|
|
70
|
+
// a subset of RFC 1123 §2.1 hostname labels but the *audience* is
|
|
71
|
+
// mail-list naming, not DNS resolution; the inline form lets the
|
|
72
|
+
// failure point name the list-id concern rather than a shared
|
|
73
|
+
// LDH primitive. Char ranges: 0x30-0x39 digits / 0x41-0x5A upper /
|
|
74
|
+
// 0x61-0x7A lower / 0x2D hyphen.
|
|
75
|
+
function _isLdhListLabel(label) {
|
|
76
|
+
if (typeof label !== "string" || label.length === 0) return false;
|
|
77
|
+
// RFC 1035 §2.3.4 — labels bounded at 63 octets.
|
|
78
|
+
if (label.length > 63) return false; // allow:raw-byte-literal — RFC 1035 §2.3.4 hostname-label limit
|
|
79
|
+
var n = label.length;
|
|
80
|
+
for (var i = 0; i < n; i += 1) {
|
|
81
|
+
var c = label.charCodeAt(i);
|
|
82
|
+
var isDigit = c >= 0x30 && c <= 0x39;
|
|
83
|
+
var isUpper = c >= 0x41 && c <= 0x5A;
|
|
84
|
+
var isLower = c >= 0x61 && c <= 0x7A;
|
|
85
|
+
var isHyphen = c === 0x2D;
|
|
86
|
+
var ok = isDigit || isUpper || isLower || (isHyphen && i > 0 && i < n - 1);
|
|
87
|
+
if (!ok) return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
function _validateListId(value, label) {
|
|
92
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
93
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
94
|
+
label + " must be a non-empty string");
|
|
95
|
+
}
|
|
96
|
+
if (value.length > HEADER_VALUE_MAX_BYTES) {
|
|
97
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
98
|
+
label + " exceeds " + HEADER_VALUE_MAX_BYTES + " byte cap");
|
|
99
|
+
}
|
|
100
|
+
if (/[\r\n\0]/.test(value)) {
|
|
101
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
102
|
+
label + " contains forbidden CR/LF/NUL byte");
|
|
103
|
+
}
|
|
104
|
+
// Accept either the label-list bare form OR `Phrase <label-list>`.
|
|
105
|
+
// Strip an optional `Phrase <...>` wrap to test the inner label list.
|
|
106
|
+
var inner = value;
|
|
107
|
+
var bracket = value.match(/<([^>]+)>\s*$/);
|
|
108
|
+
if (bracket) inner = bracket[1];
|
|
109
|
+
// Inner is dot-separated labels; each label LDH per RFC 2919 §3.
|
|
110
|
+
var labels = inner.split(".");
|
|
111
|
+
if (labels.length < 2) { // allow:raw-byte-literal — RFC 2919 §3 requires >= 2 labels
|
|
112
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
113
|
+
label + " '" + value + "' must contain at least two dot-separated labels (RFC 2919 §3)");
|
|
114
|
+
}
|
|
115
|
+
for (var i = 0; i < labels.length; i += 1) {
|
|
116
|
+
if (!_isLdhListLabel(labels[i])) {
|
|
117
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
118
|
+
label + " '" + value + "' has invalid label '" + labels[i] + "' (RFC 2919 §3 LDH)");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// If operator passed `Phrase <label-list>` form, return it as-is;
|
|
122
|
+
// RFC 2919 says Phrase is optional but allowed. Otherwise wrap the
|
|
123
|
+
// bare label-list in angle brackets per the canonical on-the-wire
|
|
124
|
+
// shape (`<lst.example.com>`).
|
|
125
|
+
return bracket ? value : "<" + value + ">";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function _validateHttpsUrl(value, label) {
|
|
129
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
130
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
131
|
+
label + " must be a non-empty string");
|
|
132
|
+
}
|
|
133
|
+
if (value.length > HEADER_VALUE_MAX_BYTES) {
|
|
134
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
135
|
+
label + " exceeds " + HEADER_VALUE_MAX_BYTES + " byte cap");
|
|
136
|
+
}
|
|
137
|
+
if (/[\r\n\0]/.test(value)) {
|
|
138
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
139
|
+
label + " contains forbidden CR/LF/NUL byte");
|
|
140
|
+
}
|
|
141
|
+
var parsed;
|
|
142
|
+
try {
|
|
143
|
+
parsed = safeUrl.parse(value, { allowedProtocols: safeUrl.ALLOW_HTTP_TLS });
|
|
144
|
+
} catch (e) {
|
|
145
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
146
|
+
label + " must be a valid https URL (got " +
|
|
147
|
+
JSON.stringify(value).slice(0, 200) + "): " + // allow:raw-byte-literal — diagnostic clamp characters
|
|
148
|
+
((e && e.message) || String(e)));
|
|
149
|
+
}
|
|
150
|
+
if (!parsed) {
|
|
151
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
152
|
+
label + " must be a valid https URL (got " +
|
|
153
|
+
JSON.stringify(value).slice(0, 200) + ")"); // allow:raw-byte-literal — diagnostic clamp characters
|
|
154
|
+
}
|
|
155
|
+
return parsed.href;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// mailto: shape validation. Accepts `mailto:addr` form OR a bare
|
|
159
|
+
// `addr@domain` form (we'll prefix `mailto:` ourselves). Refuses CR/LF/
|
|
160
|
+
// NUL injection. Doesn't validate the address with EMAIL_RE here — the
|
|
161
|
+
// addr@domain shape and length cap is what bounds smuggling risk.
|
|
162
|
+
function _validateMailto(value, label) {
|
|
163
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
164
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
165
|
+
label + " must be a non-empty string");
|
|
166
|
+
}
|
|
167
|
+
if (value.length > HEADER_VALUE_MAX_BYTES) {
|
|
168
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
169
|
+
label + " exceeds " + HEADER_VALUE_MAX_BYTES + " byte cap");
|
|
170
|
+
}
|
|
171
|
+
if (/[\r\n\0]/.test(value)) {
|
|
172
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
173
|
+
label + " contains forbidden CR/LF/NUL byte");
|
|
174
|
+
}
|
|
175
|
+
var hasScheme = value.indexOf("mailto:") === 0;
|
|
176
|
+
var inner = hasScheme ? value.slice("mailto:".length) : value;
|
|
177
|
+
// Strip query parameters before testing the addr shape.
|
|
178
|
+
var addrPart = inner.split("?")[0];
|
|
179
|
+
if (addrPart.indexOf("@") < 1 || addrPart.lastIndexOf("@") === addrPart.length - 1) {
|
|
180
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
181
|
+
label + " must be a valid `addr@domain` (with optional `mailto:` prefix)");
|
|
182
|
+
}
|
|
183
|
+
return hasScheme ? value : "mailto:" + value;
|
|
184
|
+
}
|
|
185
|
+
|
|
48
186
|
// Build the List-Unsubscribe + List-Unsubscribe-Post headers per
|
|
49
187
|
// RFC 8058 + RFC 2369. Returns a headers object suitable for merging
|
|
50
188
|
// into `b.mail.send({ headers })`.
|
|
51
189
|
function buildHeaders(opts) {
|
|
52
190
|
if (!opts || typeof opts !== "object") {
|
|
53
|
-
throw new
|
|
54
|
-
"({ url?, mailto?, oneClick? })");
|
|
191
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
192
|
+
"buildHeaders: opts object required ({ url?, mailto?, oneClick? })");
|
|
55
193
|
}
|
|
56
194
|
var parts = [];
|
|
57
195
|
if (typeof opts.url === "string" && opts.url.length > 0) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (!parsed) {
|
|
61
|
-
throw new Error("buildHeaders: opts.url must be a valid http(s) URL");
|
|
62
|
-
}
|
|
63
|
-
parts.push("<" + parsed.href + ">");
|
|
196
|
+
var href = _validateHttpsUrl(opts.url, "buildHeaders: opts.url");
|
|
197
|
+
parts.push("<" + href + ">");
|
|
64
198
|
}
|
|
65
199
|
if (typeof opts.mailto === "string" && opts.mailto.length > 0) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// do a minimal shape check.
|
|
69
|
-
if (opts.mailto.indexOf("mailto:") === 0) {
|
|
70
|
-
parts.push("<" + opts.mailto + ">");
|
|
71
|
-
} else {
|
|
72
|
-
parts.push("<mailto:" + opts.mailto + ">");
|
|
73
|
-
}
|
|
200
|
+
var mt = _validateMailto(opts.mailto, "buildHeaders: opts.mailto");
|
|
201
|
+
parts.push("<" + mt + ">");
|
|
74
202
|
}
|
|
75
203
|
if (parts.length === 0) {
|
|
76
|
-
throw new
|
|
204
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
205
|
+
"buildHeaders: at least one of opts.url / opts.mailto required");
|
|
77
206
|
}
|
|
78
207
|
var headers = { "List-Unsubscribe": parts.join(", ") };
|
|
79
208
|
if (opts.oneClick === true) {
|
|
@@ -83,6 +212,115 @@ function buildHeaders(opts) {
|
|
|
83
212
|
return headers;
|
|
84
213
|
}
|
|
85
214
|
|
|
215
|
+
// Build the full RFC 2369 / RFC 2919 List-* header bundle in one call.
|
|
216
|
+
//
|
|
217
|
+
// Single-call shape so operators set the whole list-management bundle
|
|
218
|
+
// in one place rather than juggling individual builders. Every input
|
|
219
|
+
// is shape-validated at config-time (throw at config-time) so a missing
|
|
220
|
+
// scheme / control byte / malformed list-id surfaces here, not as a
|
|
221
|
+
// downstream parser refusing the message after the network hop.
|
|
222
|
+
//
|
|
223
|
+
// var headers = b.mail.unsubscribe.buildAllListHeaders({
|
|
224
|
+
// unsubscribeUrl: "https://example.com/u?t=...",
|
|
225
|
+
// unsubscribeMailto: "unsub@example.com",
|
|
226
|
+
// oneClick: true,
|
|
227
|
+
// helpUrl: "https://example.com/list-help",
|
|
228
|
+
// archiveUrl: "https://example.com/archive",
|
|
229
|
+
// ownerEmail: "owner@example.com",
|
|
230
|
+
// postEmail: "list@example.com",
|
|
231
|
+
// listId: "lst.example.com",
|
|
232
|
+
// listOwner: "Acme List <owner@example.com>",
|
|
233
|
+
// });
|
|
234
|
+
//
|
|
235
|
+
// Returns a flat headers object with the canonical RFC casing
|
|
236
|
+
// (`List-Unsubscribe`, `List-Help`, `List-Archive`, `List-Owner`,
|
|
237
|
+
// `List-Post`, `List-ID`).
|
|
238
|
+
function buildAllListHeaders(opts) {
|
|
239
|
+
if (!opts || typeof opts !== "object") {
|
|
240
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
241
|
+
"buildAllListHeaders: opts object required");
|
|
242
|
+
}
|
|
243
|
+
var headers = {};
|
|
244
|
+
|
|
245
|
+
// List-Unsubscribe + List-Unsubscribe-Post (RFC 8058 / RFC 2369).
|
|
246
|
+
if (opts.unsubscribeUrl != null || opts.unsubscribeMailto != null ||
|
|
247
|
+
opts.oneClick !== undefined) {
|
|
248
|
+
var unsubHeaders = buildHeaders({
|
|
249
|
+
url: opts.unsubscribeUrl,
|
|
250
|
+
mailto: opts.unsubscribeMailto,
|
|
251
|
+
oneClick: opts.oneClick === true,
|
|
252
|
+
});
|
|
253
|
+
Object.assign(headers, unsubHeaders);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// List-Help (RFC 2369 §3.2). URL or mailto.
|
|
257
|
+
if (opts.helpUrl != null) {
|
|
258
|
+
headers["List-Help"] = "<" + _validateHttpsUrl(opts.helpUrl,
|
|
259
|
+
"buildAllListHeaders: opts.helpUrl") + ">";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// List-Archive (RFC 2369 §3.6). URL.
|
|
263
|
+
if (opts.archiveUrl != null) {
|
|
264
|
+
headers["List-Archive"] = "<" + _validateHttpsUrl(opts.archiveUrl,
|
|
265
|
+
"buildAllListHeaders: opts.archiveUrl") + ">";
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// List-Owner (RFC 2369 §3.3). mailto:.
|
|
269
|
+
if (opts.ownerEmail != null) {
|
|
270
|
+
headers["List-Owner"] = "<" + _validateMailto(opts.ownerEmail,
|
|
271
|
+
"buildAllListHeaders: opts.ownerEmail") + ">";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// List-Post (RFC 2369 §3.4). mailto:, or "NO" sentinel for read-only
|
|
275
|
+
// lists. RFC explicitly carves out the literal NO token.
|
|
276
|
+
if (opts.postEmail != null) {
|
|
277
|
+
if (opts.postEmail === "NO") {
|
|
278
|
+
headers["List-Post"] = "NO";
|
|
279
|
+
} else {
|
|
280
|
+
headers["List-Post"] = "<" + _validateMailto(opts.postEmail,
|
|
281
|
+
"buildAllListHeaders: opts.postEmail") + ">";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// List-ID (RFC 2919 §3). Bare label-list OR `Phrase <label-list>`.
|
|
286
|
+
if (opts.listId != null) {
|
|
287
|
+
headers["List-ID"] = _validateListId(opts.listId,
|
|
288
|
+
"buildAllListHeaders: opts.listId");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// List-Owner phrase form: an operator may pass a pre-rendered
|
|
292
|
+
// `Owner Name <addr@domain>` value directly via opts.listOwner.
|
|
293
|
+
// Overrides ownerEmail when both are provided.
|
|
294
|
+
if (opts.listOwner != null) {
|
|
295
|
+
validateOpts.requireNonEmptyString(opts.listOwner,
|
|
296
|
+
"buildAllListHeaders: opts.listOwner",
|
|
297
|
+
MailUnsubscribeError, "mailunsubscribe/invalid-list-header-shape");
|
|
298
|
+
if (opts.listOwner.length > HEADER_VALUE_MAX_BYTES) {
|
|
299
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
300
|
+
"buildAllListHeaders: opts.listOwner exceeds " + HEADER_VALUE_MAX_BYTES + " byte cap");
|
|
301
|
+
}
|
|
302
|
+
if (/[\r\n\0]/.test(opts.listOwner)) {
|
|
303
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
304
|
+
"buildAllListHeaders: opts.listOwner contains forbidden CR/LF/NUL byte");
|
|
305
|
+
}
|
|
306
|
+
// Phrase form must contain an angle-bracket address to satisfy
|
|
307
|
+
// RFC 2369 §3.3 — we don't run the full RFC 5322 phrase parser
|
|
308
|
+
// here; presence of `<...@...>` is enough to surface typos.
|
|
309
|
+
var ownerBracket = opts.listOwner.match(/<([^>]+)>/);
|
|
310
|
+
if (!ownerBracket || ownerBracket[1].indexOf("@") < 1) {
|
|
311
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
312
|
+
"buildAllListHeaders: opts.listOwner must contain `<addr@domain>` (RFC 2369 §3.3)");
|
|
313
|
+
}
|
|
314
|
+
headers["List-Owner"] = opts.listOwner;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (Object.keys(headers).length === 0) {
|
|
318
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
319
|
+
"buildAllListHeaders: at least one List-* field must be supplied");
|
|
320
|
+
}
|
|
321
|
+
return headers;
|
|
322
|
+
}
|
|
323
|
+
|
|
86
324
|
// RFC 8058 §3.1 one-click handler middleware.
|
|
87
325
|
//
|
|
88
326
|
// On POST, the body MUST contain `List-Unsubscribe=One-Click` (case-
|
|
@@ -95,8 +333,8 @@ function buildHeaders(opts) {
|
|
|
95
333
|
function handler(opts) {
|
|
96
334
|
opts = opts || {};
|
|
97
335
|
if (typeof opts.onUnsubscribe !== "function") {
|
|
98
|
-
throw new
|
|
99
|
-
"must be a function (req, res) → Promise");
|
|
336
|
+
throw new MailUnsubscribeError("mailunsubscribe/invalid-list-header-shape",
|
|
337
|
+
"mail.unsubscribe.handler: opts.onUnsubscribe must be a function (req, res) → Promise");
|
|
100
338
|
}
|
|
101
339
|
return async function unsubscribeMiddleware(req, res) {
|
|
102
340
|
if ((req.method || "").toUpperCase() !== "POST") {
|
|
@@ -108,7 +346,7 @@ function handler(opts) {
|
|
|
108
346
|
}
|
|
109
347
|
var bodyChunks = [];
|
|
110
348
|
var totalLen = 0;
|
|
111
|
-
var maxBodyBytes = opts.maxBodyBytes ||
|
|
349
|
+
var maxBodyBytes = opts.maxBodyBytes || C.BYTES.kib(4);
|
|
112
350
|
var bodyComplete = await new Promise(function (resolve) {
|
|
113
351
|
req.on("data", function (chunk) {
|
|
114
352
|
totalLen += chunk.length;
|
|
@@ -155,6 +393,8 @@ function handler(opts) {
|
|
|
155
393
|
}
|
|
156
394
|
|
|
157
395
|
module.exports = {
|
|
158
|
-
buildHeaders:
|
|
159
|
-
|
|
396
|
+
buildHeaders: buildHeaders,
|
|
397
|
+
buildAllListHeaders: buildAllListHeaders,
|
|
398
|
+
handler: handler,
|
|
399
|
+
MailUnsubscribeError: MailUnsubscribeError,
|
|
160
400
|
};
|