postal-mime 2.6.1 → 2.7.0

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.7.0](https://github.com/postalsys/postal-mime/compare/v2.6.1...v2.7.0) (2025-12-22)
4
+
5
+
6
+ ### Features
7
+
8
+ * add headerLines property exposing raw header lines ([c79a02a](https://github.com/postalsys/postal-mime/commit/c79a02ab05d9cac44e05e95a433752ff292aa5eb))
9
+
3
10
  ## [2.6.1](https://github.com/postalsys/postal-mime/compare/v2.6.0...v2.6.1) (2025-11-26)
4
11
 
5
12
 
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://kreata.ee/postal-mime/example/).
42
+ Try out a live demo using the [example page](https://postal-mime.postalsys.com/demo).
43
43
 
44
44
  ## Installation
45
45
 
@@ -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
- } else {
197
- line = line.replace(/\s+/g, " ");
198
- let sep = line.indexOf(":");
199
- let key = sep < 0 ? line.trim() : line.substr(0, sep).trim();
200
- let value = sep < 0 ? "" : line.substr(sep + 1).trim();
201
- this.headers.push({ key: key.toLowerCase(), originalKey: key, value });
202
- switch (key.toLowerCase()) {
203
- case "content-type":
204
- if (this.contentType.default) {
205
- this.contentType = { value, parsed: {} };
206
- }
207
- break;
208
- case "content-transfer-encoding":
209
- this.contentTransferEncoding = { value, parsed: {} };
210
- break;
211
- case "content-disposition":
212
- this.contentDisposition = { value, parsed: {} };
213
- break;
214
- case "content-id":
215
- this.contentId = value;
216
- break;
217
- case "content-description":
218
- this.contentDescription = value;
219
- break;
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);
@@ -426,6 +426,7 @@ class PostalMime {
426
426
  message.text = this.textContent.plain;
427
427
  }
428
428
  message.attachments = this.attachments;
429
+ message.headerLines = (this.root.rawHeaderLines || []).slice().reverse();
429
430
  switch (this.attachmentEncoding) {
430
431
  case "arraybuffer":
431
432
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postal-mime",
3
- "version": "2.6.1",
3
+ "version": "2.7.0",
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
- } else {
217
- // remove folding and extra WS
218
- line = line.replace(/\s+/g, ' ');
219
- let sep = line.indexOf(':');
220
- let key = sep < 0 ? line.trim() : line.substr(0, sep).trim();
221
- let value = sep < 0 ? '' : line.substr(sep + 1).trim();
222
- this.headers.push({ key: key.toLowerCase(), originalKey: key, value });
223
-
224
- switch (key.toLowerCase()) {
225
- case 'content-type':
226
- if (this.contentType.default) {
227
- this.contentType = { value, parsed: {} };
228
- }
229
- break;
230
- case 'content-transfer-encoding':
231
- this.contentTransferEncoding = { value, parsed: {} };
232
- break;
233
- case 'content-disposition':
234
- this.contentDisposition = { value, parsed: {} };
235
- break;
236
- case 'content-id':
237
- this.contentId = value;
238
- break;
239
- case 'content-description':
240
- this.contentDescription = value;
241
- break;
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
 
@@ -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;