postal-mime 2.6.1 → 2.7.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 +14 -0
- package/README.md +1 -1
- package/dist/mime-node.cjs +34 -25
- package/dist/postal-mime.cjs +8 -4
- package/package.json +2 -1
- package/postal-mime.d.ts +8 -0
- package/src/mime-node.js +48 -27
- package/src/postal-mime.js +7 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.7.1](https://github.com/postalsys/postal-mime/compare/v2.7.0...v2.7.1) (2025-12-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* Add null checks for contentDisposition.parsed access ([fd54c37](https://github.com/postalsys/postal-mime/commit/fd54c37093cc64737c6bb17986bc9d052d2d5add))
|
|
9
|
+
|
|
10
|
+
## [2.7.0](https://github.com/postalsys/postal-mime/compare/v2.6.1...v2.7.0) (2025-12-22)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* add headerLines property exposing raw header lines ([c79a02a](https://github.com/postalsys/postal-mime/commit/c79a02ab05d9cac44e05e95a433752ff292aa5eb))
|
|
16
|
+
|
|
3
17
|
## [2.6.1](https://github.com/postalsys/postal-mime/compare/v2.6.0...v2.6.1) (2025-11-26)
|
|
4
18
|
|
|
5
19
|
|
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ The source code is available on [GitHub](https://github.com/postalsys/postal-mim
|
|
|
39
39
|
|
|
40
40
|
## Demo
|
|
41
41
|
|
|
42
|
-
Try out a live demo using the [example page](https://
|
|
42
|
+
Try out a live demo using the [example page](https://postal-mime.postalsys.com/demo).
|
|
43
43
|
|
|
44
44
|
## Installation
|
|
45
45
|
|
package/dist/mime-node.cjs
CHANGED
|
@@ -193,31 +193,40 @@ class MimeNode {
|
|
|
193
193
|
if (i && /^\s/.test(line)) {
|
|
194
194
|
this.headerLines[i - 1] += "\n" + line;
|
|
195
195
|
this.headerLines.splice(i, 1);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
this.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
this.rawHeaderLines = [];
|
|
199
|
+
for (let i = this.headerLines.length - 1; i >= 0; i--) {
|
|
200
|
+
let rawLine = this.headerLines[i];
|
|
201
|
+
let sep = rawLine.indexOf(":");
|
|
202
|
+
let rawKey = sep < 0 ? rawLine.trim() : rawLine.substr(0, sep).trim();
|
|
203
|
+
this.rawHeaderLines.push({
|
|
204
|
+
key: rawKey.toLowerCase(),
|
|
205
|
+
line: rawLine
|
|
206
|
+
});
|
|
207
|
+
let normalizedLine = rawLine.replace(/\s+/g, " ");
|
|
208
|
+
sep = normalizedLine.indexOf(":");
|
|
209
|
+
let key = sep < 0 ? normalizedLine.trim() : normalizedLine.substr(0, sep).trim();
|
|
210
|
+
let value = sep < 0 ? "" : normalizedLine.substr(sep + 1).trim();
|
|
211
|
+
this.headers.push({ key: key.toLowerCase(), originalKey: key, value });
|
|
212
|
+
switch (key.toLowerCase()) {
|
|
213
|
+
case "content-type":
|
|
214
|
+
if (this.contentType.default) {
|
|
215
|
+
this.contentType = { value, parsed: {} };
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
case "content-transfer-encoding":
|
|
219
|
+
this.contentTransferEncoding = { value, parsed: {} };
|
|
220
|
+
break;
|
|
221
|
+
case "content-disposition":
|
|
222
|
+
this.contentDisposition = { value, parsed: {} };
|
|
223
|
+
break;
|
|
224
|
+
case "content-id":
|
|
225
|
+
this.contentId = value;
|
|
226
|
+
break;
|
|
227
|
+
case "content-description":
|
|
228
|
+
this.contentDescription = value;
|
|
229
|
+
break;
|
|
221
230
|
}
|
|
222
231
|
}
|
|
223
232
|
this.contentType.parsed = this.parseStructuredHeader(this.contentType.value);
|
package/dist/postal-mime.cjs
CHANGED
|
@@ -134,6 +134,7 @@ class PostalMime {
|
|
|
134
134
|
let textMap = this.textMap = /* @__PURE__ */ new Map();
|
|
135
135
|
let forceRfc822Attachments = this.forceRfc822Attachments();
|
|
136
136
|
let walk = async (node, alternative, related) => {
|
|
137
|
+
var _a, _b, _c, _d, _e;
|
|
137
138
|
alternative = alternative || false;
|
|
138
139
|
related = related || false;
|
|
139
140
|
if (!node.contentType.multipart) {
|
|
@@ -173,11 +174,11 @@ class PostalMime {
|
|
|
173
174
|
textEntry[textType].push({ type: "text", value: node.getTextContent() });
|
|
174
175
|
textTypes.add(textType);
|
|
175
176
|
} else if (node.content) {
|
|
176
|
-
const filename = node.contentDisposition.parsed.params.filename || node.contentType.parsed.params.name || null;
|
|
177
|
+
const filename = ((_c = (_b = (_a = node.contentDisposition) == null ? void 0 : _a.parsed) == null ? void 0 : _b.params) == null ? void 0 : _c.filename) || node.contentType.parsed.params.name || null;
|
|
177
178
|
const attachment = {
|
|
178
179
|
filename: filename ? (0, import_decode_strings.decodeWords)(filename) : null,
|
|
179
180
|
mimeType: node.contentType.parsed.value,
|
|
180
|
-
disposition: node.contentDisposition.parsed.value || null
|
|
181
|
+
disposition: ((_e = (_d = node.contentDisposition) == null ? void 0 : _d.parsed) == null ? void 0 : _e.value) || null
|
|
181
182
|
};
|
|
182
183
|
if (related && node.contentId) {
|
|
183
184
|
attachment.related = true;
|
|
@@ -285,7 +286,8 @@ class PostalMime {
|
|
|
285
286
|
this.textContent = textContent;
|
|
286
287
|
}
|
|
287
288
|
isInlineTextNode(node) {
|
|
288
|
-
|
|
289
|
+
var _a, _b;
|
|
290
|
+
if (((_b = (_a = node.contentDisposition) == null ? void 0 : _a.parsed) == null ? void 0 : _b.value) === "attachment") {
|
|
289
291
|
return false;
|
|
290
292
|
}
|
|
291
293
|
switch (node.contentType.parsed.value) {
|
|
@@ -299,10 +301,11 @@ class PostalMime {
|
|
|
299
301
|
}
|
|
300
302
|
}
|
|
301
303
|
isInlineMessageRfc822(node) {
|
|
304
|
+
var _a, _b;
|
|
302
305
|
if (node.contentType.parsed.value !== "message/rfc822") {
|
|
303
306
|
return false;
|
|
304
307
|
}
|
|
305
|
-
let disposition = node.contentDisposition.parsed.value || (this.options.rfc822Attachments ? "attachment" : "inline");
|
|
308
|
+
let disposition = ((_b = (_a = node.contentDisposition) == null ? void 0 : _a.parsed) == null ? void 0 : _b.value) || (this.options.rfc822Attachments ? "attachment" : "inline");
|
|
306
309
|
return disposition === "inline";
|
|
307
310
|
}
|
|
308
311
|
// Check if this is a specially crafted report email where message/rfc822 content should not be inlined
|
|
@@ -426,6 +429,7 @@ class PostalMime {
|
|
|
426
429
|
message.text = this.textContent.plain;
|
|
427
430
|
}
|
|
428
431
|
message.attachments = this.attachments;
|
|
432
|
+
message.headerLines = (this.root.rawHeaderLines || []).slice().reverse();
|
|
429
433
|
switch (this.attachmentEncoding) {
|
|
430
434
|
case "arraybuffer":
|
|
431
435
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postal-mime",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.1",
|
|
4
4
|
"description": "Email parser for browser environments",
|
|
5
5
|
"main": "./dist/postal-mime.cjs",
|
|
6
6
|
"module": "./src/postal-mime.js",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"bugs": {
|
|
35
35
|
"url": "https://github.com/postalsys/postal-mime/issues"
|
|
36
36
|
},
|
|
37
|
+
"homepage": "https://postal-mime.postalsys.com",
|
|
37
38
|
"author": "Andris Reinman",
|
|
38
39
|
"license": "MIT-0",
|
|
39
40
|
"devDependencies": {
|
package/postal-mime.d.ts
CHANGED
|
@@ -6,6 +6,13 @@ export type Header = {
|
|
|
6
6
|
value: string;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
export type HeaderLine = {
|
|
10
|
+
/** Lowercase header name */
|
|
11
|
+
key: string;
|
|
12
|
+
/** Complete raw header line including key and value (with folded lines merged) */
|
|
13
|
+
line: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
9
16
|
export type Mailbox = {
|
|
10
17
|
name: string;
|
|
11
18
|
address: string;
|
|
@@ -34,6 +41,7 @@ export type Attachment = {
|
|
|
34
41
|
|
|
35
42
|
export type Email = {
|
|
36
43
|
headers: Header[];
|
|
44
|
+
headerLines: HeaderLine[];
|
|
37
45
|
from?: Address;
|
|
38
46
|
sender?: Address;
|
|
39
47
|
replyTo?: Address[];
|
package/src/mime-node.js
CHANGED
|
@@ -208,38 +208,59 @@ export default class MimeNode {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
processHeaders() {
|
|
211
|
+
// First pass: merge folded headers (backward iteration)
|
|
211
212
|
for (let i = this.headerLines.length - 1; i >= 0; i--) {
|
|
212
213
|
let line = this.headerLines[i];
|
|
213
214
|
if (i && /^\s/.test(line)) {
|
|
214
215
|
this.headerLines[i - 1] += '\n' + line;
|
|
215
216
|
this.headerLines.splice(i, 1);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Initialize rawHeaderLines to store unmodified lines
|
|
221
|
+
this.rawHeaderLines = [];
|
|
222
|
+
|
|
223
|
+
// Second pass: process headers (MUST be backward to maintain this.headers order)
|
|
224
|
+
// The existing code iterates backward and postal-mime.js calls .reverse()
|
|
225
|
+
// We must preserve this behavior to avoid breaking changes
|
|
226
|
+
for (let i = this.headerLines.length - 1; i >= 0; i--) {
|
|
227
|
+
let rawLine = this.headerLines[i];
|
|
228
|
+
|
|
229
|
+
// Extract key from raw line for rawHeaderLines
|
|
230
|
+
let sep = rawLine.indexOf(':');
|
|
231
|
+
let rawKey = sep < 0 ? rawLine.trim() : rawLine.substr(0, sep).trim();
|
|
232
|
+
|
|
233
|
+
// Store raw line with lowercase key
|
|
234
|
+
this.rawHeaderLines.push({
|
|
235
|
+
key: rawKey.toLowerCase(),
|
|
236
|
+
line: rawLine
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Normalize for this.headers (existing behavior - order preserved)
|
|
240
|
+
let normalizedLine = rawLine.replace(/\s+/g, ' ');
|
|
241
|
+
sep = normalizedLine.indexOf(':');
|
|
242
|
+
let key = sep < 0 ? normalizedLine.trim() : normalizedLine.substr(0, sep).trim();
|
|
243
|
+
let value = sep < 0 ? '' : normalizedLine.substr(sep + 1).trim();
|
|
244
|
+
this.headers.push({ key: key.toLowerCase(), originalKey: key, value });
|
|
245
|
+
|
|
246
|
+
switch (key.toLowerCase()) {
|
|
247
|
+
case 'content-type':
|
|
248
|
+
if (this.contentType.default) {
|
|
249
|
+
this.contentType = { value, parsed: {} };
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
case 'content-transfer-encoding':
|
|
253
|
+
this.contentTransferEncoding = { value, parsed: {} };
|
|
254
|
+
break;
|
|
255
|
+
case 'content-disposition':
|
|
256
|
+
this.contentDisposition = { value, parsed: {} };
|
|
257
|
+
break;
|
|
258
|
+
case 'content-id':
|
|
259
|
+
this.contentId = value;
|
|
260
|
+
break;
|
|
261
|
+
case 'content-description':
|
|
262
|
+
this.contentDescription = value;
|
|
263
|
+
break;
|
|
243
264
|
}
|
|
244
265
|
}
|
|
245
266
|
|
package/src/postal-mime.js
CHANGED
|
@@ -200,11 +200,11 @@ export default class PostalMime {
|
|
|
200
200
|
// is it an attachment
|
|
201
201
|
else if (node.content) {
|
|
202
202
|
const filename =
|
|
203
|
-
node.contentDisposition
|
|
203
|
+
node.contentDisposition?.parsed?.params?.filename || node.contentType.parsed.params.name || null;
|
|
204
204
|
const attachment = {
|
|
205
205
|
filename: filename ? decodeWords(filename) : null,
|
|
206
206
|
mimeType: node.contentType.parsed.value,
|
|
207
|
-
disposition: node.contentDisposition
|
|
207
|
+
disposition: node.contentDisposition?.parsed?.value || null
|
|
208
208
|
};
|
|
209
209
|
|
|
210
210
|
if (related && node.contentId) {
|
|
@@ -333,7 +333,7 @@ export default class PostalMime {
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
isInlineTextNode(node) {
|
|
336
|
-
if (node.contentDisposition
|
|
336
|
+
if (node.contentDisposition?.parsed?.value === 'attachment') {
|
|
337
337
|
// no matter the type, this is an attachment
|
|
338
338
|
return false;
|
|
339
339
|
}
|
|
@@ -355,7 +355,7 @@ export default class PostalMime {
|
|
|
355
355
|
return false;
|
|
356
356
|
}
|
|
357
357
|
let disposition =
|
|
358
|
-
node.contentDisposition
|
|
358
|
+
node.contentDisposition?.parsed?.value || (this.options.rfc822Attachments ? 'attachment' : 'inline');
|
|
359
359
|
return disposition === 'inline';
|
|
360
360
|
}
|
|
361
361
|
|
|
@@ -517,6 +517,9 @@ export default class PostalMime {
|
|
|
517
517
|
|
|
518
518
|
message.attachments = this.attachments;
|
|
519
519
|
|
|
520
|
+
// Expose raw header lines (reversed to match headers array order)
|
|
521
|
+
message.headerLines = (this.root.rawHeaderLines || []).slice().reverse();
|
|
522
|
+
|
|
520
523
|
switch (this.attachmentEncoding) {
|
|
521
524
|
case 'arraybuffer':
|
|
522
525
|
break;
|