arn-rawmime 0.0.5 → 0.0.6
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/package.json +6 -14
- package/src/dkim-signer.js +14 -14
- package/src/rawmimeBuilder.js +10 -7
- package/src/utility/mailParser.d.ts +2 -2
- package/src/utility/mailParser.js +2 -2
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arn-rawmime",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "A lightweight, dependency-free raw MIME email builder with DKIM support.",
|
|
5
5
|
"author": "ARNDESK",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=16.0.0"
|
|
9
|
+
},
|
|
7
10
|
"main": "src/index.js",
|
|
8
11
|
"types": "src/index.d.ts",
|
|
9
12
|
"files": [
|
|
@@ -13,22 +16,11 @@
|
|
|
13
16
|
"test": "test"
|
|
14
17
|
},
|
|
15
18
|
"dependencies": {
|
|
16
|
-
"deepmerge": "^4.3.1",
|
|
17
|
-
"dom-serializer": "^2.0.0",
|
|
18
|
-
"domelementtype": "^2.3.0",
|
|
19
|
-
"domhandler": "^5.0.3",
|
|
20
|
-
"domutils": "^3.2.2",
|
|
21
|
-
"entities": "^4.5.0",
|
|
22
19
|
"he": "^1.2.0",
|
|
23
|
-
"html-to-text": "^
|
|
24
|
-
"htmlparser2": "^8.0.2",
|
|
25
|
-
"leac": "^0.6.0",
|
|
20
|
+
"html-to-text": "^10.0.0",
|
|
26
21
|
"mailparser": "^3.9.0",
|
|
27
|
-
"marked": "^
|
|
22
|
+
"marked": "^18.0.3",
|
|
28
23
|
"mime-types": "^3.0.2",
|
|
29
|
-
"parseley": "^0.12.1",
|
|
30
|
-
"peberminta": "^0.9.0",
|
|
31
|
-
"selderee": "^0.11.0",
|
|
32
24
|
"turndown": "^7.2.2"
|
|
33
25
|
},
|
|
34
26
|
"scripts": {
|
package/src/dkim-signer.js
CHANGED
|
@@ -68,12 +68,12 @@ function DKIMSign(email, options) {
|
|
|
68
68
|
options = options || {};
|
|
69
69
|
email = (email || "").toString("utf-8");
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
let match = email.match(/^\r?\n|(?:\r?\n){2}/),
|
|
72
72
|
headers = (match && email.substring(0, match.index)) || "",
|
|
73
73
|
body = (match && email.substring(match.index + match[0].length)) || email;
|
|
74
74
|
|
|
75
75
|
// All listed fields from RFC4871 #5.5
|
|
76
|
-
|
|
76
|
+
const defaultFieldNames =
|
|
77
77
|
"From:Sender:Reply-To:Subject:To:" +
|
|
78
78
|
"Cc:MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID:" +
|
|
79
79
|
"Content-Description:Resent-Date:Resent-From:Resent-Sender:" +
|
|
@@ -82,7 +82,7 @@ function DKIMSign(email, options) {
|
|
|
82
82
|
"List-Owner:List-Archive:" +
|
|
83
83
|
"List-Unsubscribe-Post:Date:Message-ID:Feedback-ID:DKIM-Signature:";
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
let dkim = generateDKIMHeader(
|
|
86
86
|
options.domainName,
|
|
87
87
|
options.keySelector,
|
|
88
88
|
options.headerFieldNames || defaultFieldNames,
|
|
@@ -117,7 +117,7 @@ function DKIMSign(email, options) {
|
|
|
117
117
|
* @return {String} Mime folded DKIM-Signature string
|
|
118
118
|
*/
|
|
119
119
|
function generateDKIMHeader(domainName, keySelector, headerFieldNames, headers, body) {
|
|
120
|
-
|
|
120
|
+
let canonicalizedBody = DKIMCanonicalizer.relaxedBody(body),
|
|
121
121
|
canonicalizedBodyHash = sha256(canonicalizedBody, "base64"),
|
|
122
122
|
canonicalizedHeaderData = DKIMCanonicalizer.relaxedHeaders(headers, headerFieldNames),
|
|
123
123
|
dkim;
|
|
@@ -176,7 +176,7 @@ const DKIMCanonicalizer = {
|
|
|
176
176
|
* @return {Object} Canonicalized headers and field names
|
|
177
177
|
*/
|
|
178
178
|
relaxedHeaders: function (headers, fieldNames) {
|
|
179
|
-
|
|
179
|
+
let includedFields = (fieldNames || "").split(":").map(function (field) {
|
|
180
180
|
return field.trim();
|
|
181
181
|
}),
|
|
182
182
|
includedFieldsLower = includedFields.map(function (field) {
|
|
@@ -197,7 +197,7 @@ const DKIMCanonicalizer = {
|
|
|
197
197
|
// Process header lines
|
|
198
198
|
for (i = 0; i < headerLines.length; i++) {
|
|
199
199
|
line = DKIMCanonicalizer.relaxedHeaderLine(headerLines[i]);
|
|
200
|
-
|
|
200
|
+
let keyLower = line.key.toLowerCase();
|
|
201
201
|
|
|
202
202
|
if (includedFieldsLower.indexOf(keyLower) >= 0 && !(keyLower in headerFields)) {
|
|
203
203
|
headerFields[keyLower] = {
|
|
@@ -207,20 +207,20 @@ const DKIMCanonicalizer = {
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
let resultHeaders = [];
|
|
211
|
+
let usedFields = [];
|
|
212
212
|
for (i = includedFields.length - 1; i >= 0; i--) {
|
|
213
|
-
|
|
213
|
+
let keyLower = includedFields[i].toLowerCase();
|
|
214
214
|
if (!headerFields[keyLower]) {
|
|
215
215
|
includedFields.splice(i, 1);
|
|
216
216
|
} else {
|
|
217
|
-
|
|
217
|
+
resultHeaders.unshift(headerFields[keyLower].key.toLowerCase() + ":" + headerFields[keyLower].value);
|
|
218
218
|
usedFields.unshift(headerFields[keyLower].key);
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
return {
|
|
223
|
-
headers:
|
|
223
|
+
headers: resultHeaders.join("\r\n") + "\r\n",
|
|
224
224
|
fieldNames: usedFields.join(":"),
|
|
225
225
|
};
|
|
226
226
|
},
|
|
@@ -231,7 +231,7 @@ const DKIMCanonicalizer = {
|
|
|
231
231
|
* @return {Object} Parsed header line with key and value
|
|
232
232
|
*/
|
|
233
233
|
relaxedHeaderLine: function (line) {
|
|
234
|
-
|
|
234
|
+
let parts = line.split(":"),
|
|
235
235
|
key = (parts.shift() || "").trim(),
|
|
236
236
|
value = parts.join(":").replace(/\s+/g, " ").trim();
|
|
237
237
|
|
|
@@ -250,7 +250,7 @@ const DKIMCanonicalizer = {
|
|
|
250
250
|
* @return {String} SHA-256 hash in the selected output encoding
|
|
251
251
|
*/
|
|
252
252
|
function sha256(str, encoding) {
|
|
253
|
-
|
|
253
|
+
const shasum = crypto.createHash("sha256");
|
|
254
254
|
shasum.update(str);
|
|
255
255
|
return shasum.digest(encoding || "hex");
|
|
256
256
|
}
|
|
@@ -262,7 +262,7 @@ function sha256(str, encoding) {
|
|
|
262
262
|
* @return {Boolean} true if the string contains non-ASCII symbols
|
|
263
263
|
*/
|
|
264
264
|
function hasUTFChars(str) {
|
|
265
|
-
|
|
265
|
+
const rforeign = /[^\u0000-\u007f]/;
|
|
266
266
|
return !!rforeign.test(str);
|
|
267
267
|
}
|
|
268
268
|
|
package/src/rawmimeBuilder.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from "path";
|
|
|
4
4
|
import { randomBytes } from "crypto";
|
|
5
5
|
import { convert } from "html-to-text";
|
|
6
6
|
import { DKIMSign } from "./dkim-signer.js";
|
|
7
|
-
import { processMarkDown } from "./processMarkDown.js";
|
|
7
|
+
import { processMarkDown } from "./processMarkDown.js";
|
|
8
8
|
|
|
9
9
|
const desiredOrder = [
|
|
10
10
|
"List-Unsubscribe-Post",
|
|
@@ -27,7 +27,7 @@ class MimeMessage {
|
|
|
27
27
|
this.bodyParts = [];
|
|
28
28
|
this.attachments = [];
|
|
29
29
|
this.attachmentPromises = [];
|
|
30
|
-
this.processingPromises = [];
|
|
30
|
+
this.processingPromises = [];
|
|
31
31
|
this.removedHeaders = new Set();
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -38,7 +38,7 @@ class MimeMessage {
|
|
|
38
38
|
wordwrap: 130,
|
|
39
39
|
selectors: [
|
|
40
40
|
{ selector: "img", format: "skip" },
|
|
41
|
-
{ selector: "a", options: { hideLinkHrefIfSameAsText: true } },
|
|
41
|
+
{ selector: "a", options: { hideLinkHrefIfSameAsText: true, linkBrackets: ["<", ">"] } },
|
|
42
42
|
],
|
|
43
43
|
});
|
|
44
44
|
}
|
|
@@ -212,7 +212,10 @@ class MimeMessage {
|
|
|
212
212
|
formatMailboxes(mailboxes) {
|
|
213
213
|
if (!mailboxes) return "";
|
|
214
214
|
const mbs = Array.isArray(mailboxes) ? mailboxes : [mailboxes];
|
|
215
|
-
return mbs.map((mb) =>
|
|
215
|
+
return mbs.map((mb) => {
|
|
216
|
+
if (typeof mb === "string") return mb;
|
|
217
|
+
return mb.name ? `"${mb.name}" <${mb.email}>` : mb.email;
|
|
218
|
+
}).join(", ");
|
|
216
219
|
}
|
|
217
220
|
|
|
218
221
|
setFrom(sender) {
|
|
@@ -376,7 +379,7 @@ class MimeMessage {
|
|
|
376
379
|
encoding = "quoted-printable",
|
|
377
380
|
autoTextPart = false,
|
|
378
381
|
autoMinify = true,
|
|
379
|
-
markdownToHtml = false,
|
|
382
|
+
markdownToHtml = false,
|
|
380
383
|
}) {
|
|
381
384
|
if (markdownToHtml) {
|
|
382
385
|
// Push the async operation to the queue
|
|
@@ -608,8 +611,8 @@ class MimeMessage {
|
|
|
608
611
|
"references",
|
|
609
612
|
"in-reply-to",
|
|
610
613
|
"content-id",
|
|
611
|
-
"list-unsubscribe",
|
|
612
|
-
"list-unsubscribe-post",
|
|
614
|
+
"list-unsubscribe",
|
|
615
|
+
"list-unsubscribe-post",
|
|
613
616
|
];
|
|
614
617
|
|
|
615
618
|
if (doNotFold.includes(lowerName)) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/utility/mailParser.d.ts
|
|
2
|
-
import type {
|
|
2
|
+
import type { MarkdownResult } from "../processMarkDown";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Generates a random numeric ID string for Doublelist posts.
|
|
@@ -50,7 +50,7 @@ export interface ParsedMailData {
|
|
|
50
50
|
/** HTML body with tracking images removed */
|
|
51
51
|
html: string;
|
|
52
52
|
/** Processed markdown result from processMarkDown */
|
|
53
|
-
markdownResult:
|
|
53
|
+
markdownResult: MarkdownResult;
|
|
54
54
|
/** Plain text body */
|
|
55
55
|
text: string;
|
|
56
56
|
/** Number of attachments */
|
|
@@ -11,8 +11,8 @@ export const randomizeDoublelistPostID = (length = 11) => {
|
|
|
11
11
|
// 1. Pick the first digit (1-9)
|
|
12
12
|
let result = firstDigit.charAt(Math.floor(Math.random() * firstDigit.length));
|
|
13
13
|
// 2. Generate the rest, stopping 1 digit early
|
|
14
|
-
// If length is 11, this loop generates
|
|
15
|
-
for (let i = 1; i < length
|
|
14
|
+
// If length is 11, this loop generates 10 more digits (Total: 11)
|
|
15
|
+
for (let i = 1; i < length; i++) {
|
|
16
16
|
result += restDigits.charAt(Math.floor(Math.random() * restDigits.length));
|
|
17
17
|
}
|
|
18
18
|
return result;
|