postal-mime 2.6.0 → 2.6.1
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 +8 -0
- package/dist/address-parser.cjs +8 -3
- package/package.json +6 -6
- package/postal-mime.d.ts +2 -1
- package/src/address-parser.js +19 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.6.1](https://github.com/postalsys/postal-mime/compare/v2.6.0...v2.6.1) (2025-11-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* prevent DoS from deeply nested address groups ([f509eaf](https://github.com/postalsys/postal-mime/commit/f509eafec31bf448482133041c086f7aefa2b3fa))
|
|
9
|
+
* update TypeScript typings to match source code ([df5640a](https://github.com/postalsys/postal-mime/commit/df5640a3166b0a1fe3ca563e1364301bb4e6a025))
|
|
10
|
+
|
|
3
11
|
## [2.6.0](https://github.com/postalsys/postal-mime/compare/v2.5.0...v2.6.0) (2025-10-24)
|
|
4
12
|
|
|
5
13
|
|
package/dist/address-parser.cjs
CHANGED
|
@@ -22,7 +22,7 @@ __export(address_parser_exports, {
|
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(address_parser_exports);
|
|
24
24
|
var import_decode_strings = require("./decode-strings.cjs");
|
|
25
|
-
function _handleAddress(tokens) {
|
|
25
|
+
function _handleAddress(tokens, depth) {
|
|
26
26
|
let isGroup = false;
|
|
27
27
|
let state = "text";
|
|
28
28
|
let address;
|
|
@@ -90,7 +90,7 @@ function _handleAddress(tokens) {
|
|
|
90
90
|
data.text = data.text.join(" ");
|
|
91
91
|
let groupMembers = [];
|
|
92
92
|
if (data.group.length) {
|
|
93
|
-
let parsedGroup = addressParser(data.group.join(","));
|
|
93
|
+
let parsedGroup = addressParser(data.group.join(","), { _depth: depth + 1 });
|
|
94
94
|
parsedGroup.forEach((member) => {
|
|
95
95
|
if (member.group) {
|
|
96
96
|
groupMembers = groupMembers.concat(member.group);
|
|
@@ -258,8 +258,13 @@ class Tokenizer {
|
|
|
258
258
|
this.escaped = false;
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
|
+
const MAX_NESTED_GROUP_DEPTH = 50;
|
|
261
262
|
function addressParser(str, options) {
|
|
262
263
|
options = options || {};
|
|
264
|
+
let depth = options._depth || 0;
|
|
265
|
+
if (depth > MAX_NESTED_GROUP_DEPTH) {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
263
268
|
let tokenizer = new Tokenizer(str);
|
|
264
269
|
let tokens = tokenizer.tokenize();
|
|
265
270
|
let addresses = [];
|
|
@@ -279,7 +284,7 @@ function addressParser(str, options) {
|
|
|
279
284
|
addresses.push(address);
|
|
280
285
|
}
|
|
281
286
|
addresses.forEach((address2) => {
|
|
282
|
-
address2 = _handleAddress(address2);
|
|
287
|
+
address2 = _handleAddress(address2, depth);
|
|
283
288
|
if (address2.length) {
|
|
284
289
|
parsedAddresses = parsedAddresses.concat(address2);
|
|
285
290
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postal-mime",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "Email parser for browser environments",
|
|
5
5
|
"main": "./dist/postal-mime.cjs",
|
|
6
6
|
"module": "./src/postal-mime.js",
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
"author": "Andris Reinman",
|
|
38
38
|
"license": "MIT-0",
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@types/node": "24.
|
|
40
|
+
"@types/node": "24.10.1",
|
|
41
41
|
"cross-blob": "3.0.2",
|
|
42
|
-
"cross-env": "
|
|
43
|
-
"esbuild": "
|
|
42
|
+
"cross-env": "10.1.0",
|
|
43
|
+
"esbuild": "0.27.0",
|
|
44
44
|
"eslint": "8.57.0",
|
|
45
45
|
"eslint-cli": "1.1.1",
|
|
46
46
|
"iframe-resizer": "4.3.6",
|
|
47
|
-
"prettier": "
|
|
48
|
-
"typescript": "
|
|
47
|
+
"prettier": "3.6.2",
|
|
48
|
+
"typescript": "5.9.3"
|
|
49
49
|
}
|
|
50
50
|
}
|
package/postal-mime.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type RawEmail = string | ArrayBuffer | Uint8Array | Blob | Buffer | Reada
|
|
|
2
2
|
|
|
3
3
|
export type Header = {
|
|
4
4
|
key: string;
|
|
5
|
+
originalKey: string;
|
|
5
6
|
value: string;
|
|
6
7
|
};
|
|
7
8
|
|
|
@@ -27,7 +28,7 @@ export type Attachment = {
|
|
|
27
28
|
description?: string;
|
|
28
29
|
contentId?: string;
|
|
29
30
|
method?: string;
|
|
30
|
-
content: ArrayBuffer | string;
|
|
31
|
+
content: ArrayBuffer | Uint8Array | string;
|
|
31
32
|
encoding?: "base64" | "utf8";
|
|
32
33
|
};
|
|
33
34
|
|
package/src/address-parser.js
CHANGED
|
@@ -4,9 +4,10 @@ import { decodeWords } from './decode-strings.js';
|
|
|
4
4
|
* Converts tokens for a single address into an address object
|
|
5
5
|
*
|
|
6
6
|
* @param {Array} tokens Tokens object
|
|
7
|
+
* @param {Number} depth Current recursion depth for nested group protection
|
|
7
8
|
* @return {Object} Address object
|
|
8
9
|
*/
|
|
9
|
-
function _handleAddress(tokens) {
|
|
10
|
+
function _handleAddress(tokens, depth) {
|
|
10
11
|
let isGroup = false;
|
|
11
12
|
let state = 'text';
|
|
12
13
|
let address;
|
|
@@ -87,7 +88,7 @@ function _handleAddress(tokens) {
|
|
|
87
88
|
// Parse group members, but flatten any nested groups (RFC 5322 doesn't allow nesting)
|
|
88
89
|
let groupMembers = [];
|
|
89
90
|
if (data.group.length) {
|
|
90
|
-
let parsedGroup = addressParser(data.group.join(','));
|
|
91
|
+
let parsedGroup = addressParser(data.group.join(','), { _depth: depth + 1 });
|
|
91
92
|
// Flatten: if any member is itself a group, extract its members into the sequence
|
|
92
93
|
parsedGroup.forEach(member => {
|
|
93
94
|
if (member.group) {
|
|
@@ -307,6 +308,13 @@ class Tokenizer {
|
|
|
307
308
|
}
|
|
308
309
|
}
|
|
309
310
|
|
|
311
|
+
/**
|
|
312
|
+
* Maximum recursion depth for parsing nested groups.
|
|
313
|
+
* RFC 5322 doesn't allow nested groups, so this is a safeguard against
|
|
314
|
+
* malicious input that could cause stack overflow.
|
|
315
|
+
*/
|
|
316
|
+
const MAX_NESTED_GROUP_DEPTH = 50;
|
|
317
|
+
|
|
310
318
|
/**
|
|
311
319
|
* Parses structured e-mail addresses from an address field
|
|
312
320
|
*
|
|
@@ -319,10 +327,18 @@ class Tokenizer {
|
|
|
319
327
|
* [{name: 'Name', address: 'address@domain'}]
|
|
320
328
|
*
|
|
321
329
|
* @param {String} str Address field
|
|
330
|
+
* @param {Object} options Optional options object
|
|
331
|
+
* @param {Number} options._depth Internal recursion depth counter (do not set manually)
|
|
322
332
|
* @return {Array} An array of address objects
|
|
323
333
|
*/
|
|
324
334
|
function addressParser(str, options) {
|
|
325
335
|
options = options || {};
|
|
336
|
+
let depth = options._depth || 0;
|
|
337
|
+
|
|
338
|
+
// Prevent stack overflow from deeply nested groups (DoS protection)
|
|
339
|
+
if (depth > MAX_NESTED_GROUP_DEPTH) {
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
326
342
|
|
|
327
343
|
let tokenizer = new Tokenizer(str);
|
|
328
344
|
let tokens = tokenizer.tokenize();
|
|
@@ -347,7 +363,7 @@ function addressParser(str, options) {
|
|
|
347
363
|
}
|
|
348
364
|
|
|
349
365
|
addresses.forEach(address => {
|
|
350
|
-
address = _handleAddress(address);
|
|
366
|
+
address = _handleAddress(address, depth);
|
|
351
367
|
if (address.length) {
|
|
352
368
|
parsedAddresses = parsedAddresses.concat(address);
|
|
353
369
|
}
|