postal-mime 2.2.2 → 2.2.4

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/.ncurc.js ADDED
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ upgrade: true,
3
+ reject: [
4
+ // api changes, check and fix
5
+ 'eslint'
6
+ ]
7
+ };
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.2.4](https://github.com/postalsys/postal-mime/compare/v2.2.3...v2.2.4) (2024-04-11)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **exports:** Export addressParser and decodeWords functions ([43d3187](https://github.com/postalsys/postal-mime/commit/43d31873308d8eff61876f32614e5cc5143c90dd))
9
+
10
+ ## [2.2.3](https://github.com/postalsys/postal-mime/compare/v2.2.2...v2.2.3) (2024-04-11)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **attachments:** Added description key from Content-Description attachment header ([6e29de9](https://github.com/postalsys/postal-mime/commit/6e29de97a4dc0043587a59870d52250602801e3c))
16
+ * **calendar-attachments:** treat text/calendar as an attachment ([2196b49](https://github.com/postalsys/postal-mime/commit/2196b497f289697e9dc72011708e4355ee7362cc))
17
+
3
18
  ## [2.2.2](https://github.com/postalsys/postal-mime/compare/v2.2.1...v2.2.2) (2024-04-10)
4
19
 
5
20
 
package/README.md CHANGED
@@ -123,6 +123,57 @@ This method parses an email message into a structured object with the following
123
123
  - **attachment[].contentId** is the ID from Content-ID header
124
124
  - **attachment[].content** is an Uint8Array value that contains the attachment file
125
125
 
126
+ ### Utility functions
127
+
128
+ #### addressParser
129
+
130
+ Parse email address strings
131
+
132
+ ```js
133
+ addressParser(addressStr, opts) -> Array
134
+ ```
135
+
136
+ where
137
+
138
+ - **addressStr** is the header value for an address header
139
+ - **opts** is an optional options object
140
+ - **flattem** is a boolean value. If set to `true`, then ignores address groups and returns a flat array of addresses. By default (`flatten` is `false`) the result might include nested groups
141
+
142
+ The result is an array of objects
143
+
144
+ - **name** is the name string. An empty string is used if name value was not set.
145
+ - **address** is the email address value
146
+
147
+ ```js
148
+ import { addressParser } from 'postal-mime';
149
+
150
+ const addressStr = '=?utf-8?B?44Ko44Od44K544Kr44O844OJ?= <support@example.com>';
151
+ console.log(addressParser(addressStr));
152
+ // [ { name: 'エポスカード', address: 'support@example.com' } ]
153
+ ```
154
+
155
+ #### decodeWords
156
+
157
+ Decode MIME encoded-words
158
+
159
+ ```js
160
+ decodeWords(encodedStr) -> String
161
+ ```
162
+
163
+ where
164
+
165
+ - **encodedStr** is a string value that _may_ include MIME encoded-words
166
+
167
+ The result is a unicode string
168
+
169
+ ```js
170
+ import { decodeWords } from 'postal-mime';
171
+
172
+ const encodedStr = 'Hello, =?utf-8?B?44Ko44Od44K544Kr44O844OJ?=';
173
+ console.log(decodeWords(encodedStr));
174
+ // Hello, エポスカード
175
+ ```
176
+
126
177
  ## License
127
178
 
128
179
  &copy; 2021-2024 Andris Reinman
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postal-mime",
3
- "version": "2.2.2",
3
+ "version": "2.2.4",
4
4
  "description": "Email parser for browser environments",
5
5
  "main": "./src/postal-mime.js",
6
6
  "exports": {
@@ -27,7 +27,7 @@
27
27
  "author": "Andris Reinman",
28
28
  "license": "MIT-0",
29
29
  "devDependencies": {
30
- "@types/node": "20.11.30",
30
+ "@types/node": "20.12.7",
31
31
  "cross-blob": "3.0.2",
32
32
  "cross-env": "7.0.3",
33
33
  "eslint": "8.57.0",
package/postal-mime.d.ts CHANGED
@@ -8,11 +8,13 @@ export type Address = {
8
8
  };
9
9
 
10
10
  export type Attachment = {
11
- filename: string;
11
+ filename: string | null;
12
12
  mimeType: string;
13
13
  disposition: "attachment" | "inline" | null;
14
14
  related?: boolean;
15
+ description?: string;
15
16
  contentId?: string;
17
+ method?: string;
16
18
  content: Uint8Array;
17
19
  };
18
20
 
@@ -36,9 +38,23 @@ export type Email = {
36
38
  attachments: Attachment[];
37
39
  };
38
40
 
41
+ declare type AddressParserOptions = {
42
+ flatten?: boolean
43
+ }
44
+
45
+ declare function addressParser (
46
+ str: string,
47
+ opts?: AddressParserOptions
48
+ ): Address[];
49
+
50
+ declare function decodeWords (
51
+ str: string
52
+ ): string;
53
+
39
54
  declare class PostalMime {
40
55
  static parse(email: RawEmail): Promise<Email>;
41
56
  parse(email: RawEmail): Promise<Email>;
42
57
  }
43
58
 
59
+ export { addressParser, decodeWords };
44
60
  export default PostalMime;
package/src/mime-node.js CHANGED
@@ -226,6 +226,9 @@ export default class MimeNode {
226
226
  case 'content-id':
227
227
  this.contentId = value;
228
228
  break;
229
+ case 'content-description':
230
+ this.contentDescription = value;
231
+ break;
229
232
  }
230
233
  }
231
234
  }
@@ -3,6 +3,8 @@ import { textToHtml, htmlToText, formatTextHeader, formatHtmlHeader } from './te
3
3
  import addressParser from './address-parser.js';
4
4
  import { decodeWords, textEncoder, blobToArrayBuffer } from './decode-strings.js';
5
5
 
6
+ export { addressParser, decodeWords };
7
+
6
8
  export default class PostalMime {
7
9
  static parse(buf) {
8
10
  const parser = new PostalMime();
@@ -162,10 +164,7 @@ export default class PostalMime {
162
164
  }
163
165
 
164
166
  // is it text?
165
- else if (
166
- (/^text\//i.test(node.contentType.parsed.value) || node.contentType.parsed.value === 'message/delivery-status') &&
167
- node.contentDisposition.parsed.value !== 'attachment'
168
- ) {
167
+ else if (this.isInlineTextNode(node)) {
169
168
  let textType = node.contentType.parsed.value.substr(node.contentType.parsed.value.indexOf('/') + 1);
170
169
  if (node.contentType.parsed.value === 'message/delivery-status') {
171
170
  textType = 'plain';
@@ -184,9 +183,9 @@ export default class PostalMime {
184
183
 
185
184
  // is it an attachment
186
185
  else if (node.content) {
187
- let filename = node.contentDisposition.parsed.params.filename || node.contentType.parsed.params.name || null;
188
- let attachment = {
189
- filename: decodeWords(filename),
186
+ const filename = node.contentDisposition.parsed.params.filename || node.contentType.parsed.params.name || null;
187
+ const attachment = {
188
+ filename: filename ? decodeWords(filename) : null,
190
189
  mimeType: node.contentType.parsed.value,
191
190
  disposition: node.contentDisposition.parsed.value || null
192
191
  };
@@ -195,11 +194,39 @@ export default class PostalMime {
195
194
  attachment.related = true;
196
195
  }
197
196
 
197
+ if (node.contentDescription) {
198
+ attachment.description = node.contentDescription;
199
+ }
200
+
198
201
  if (node.contentId) {
199
202
  attachment.contentId = node.contentId;
200
203
  }
201
204
 
202
- attachment.content = node.content;
205
+ switch (node.contentType.parsed.value) {
206
+ // Special handling for calendar events
207
+ case 'text/calendar':
208
+ case 'application/ics': {
209
+ if (node.contentType.parsed.params.method) {
210
+ attachment.method = node.contentType.parsed.params.method.toString().toUpperCase().trim();
211
+ }
212
+
213
+ // Enforce into unicode
214
+ const decodedText = node.getTextContent().replace(/\r?\n/g, '\n').replace(/\n*$/, '\n');
215
+ attachment.content = textEncoder.encode(decodedText);
216
+ break;
217
+ }
218
+
219
+ case 'message/delivery-status': {
220
+ // Enforce into unicode
221
+ const decodedText = node.getTextContent().replace(/\r?\n/g, '\n').replace(/\n*$/, '\n');
222
+ attachment.content = textEncoder.encode(decodedText);
223
+ break;
224
+ }
225
+
226
+ // Regular attachments
227
+ default:
228
+ attachment.content = node.content;
229
+ }
203
230
 
204
231
  this.attachments.push(attachment);
205
232
  }
@@ -292,6 +319,26 @@ export default class PostalMime {
292
319
  this.textContent = textContent;
293
320
  }
294
321
 
322
+ isInlineTextNode(node) {
323
+ if (node.contentDisposition.parsed.value === 'attachment') {
324
+ // no matter the type, this is an attachment
325
+ return false;
326
+ }
327
+
328
+ switch (node.contentType.parsed.value) {
329
+ case 'text/html':
330
+ case 'text/plain':
331
+ // message/delivery-status is cast into regular plaintext content
332
+ case 'message/delivery-status':
333
+ return true;
334
+
335
+ case 'text/calendar':
336
+ case 'text/csv':
337
+ default:
338
+ return false;
339
+ }
340
+ }
341
+
295
342
  async resolveStream(stream) {
296
343
  let chunkLen = 0;
297
344
  let chunks = [];
@@ -418,11 +465,11 @@ export default class PostalMime {
418
465
  message.date = date;
419
466
  }
420
467
 
421
- if (this.textContent && this.textContent.html) {
468
+ if (this.textContent?.html) {
422
469
  message.html = this.textContent.html;
423
470
  }
424
471
 
425
- if (this.textContent && this.textContent.plain) {
472
+ if (this.textContent?.plain) {
426
473
  message.text = this.textContent.plain;
427
474
  }
428
475