@holo-js/mail 0.1.5 → 0.1.7
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/dist/index.mjs +32 -5
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -17,8 +17,9 @@ import {
|
|
|
17
17
|
|
|
18
18
|
// src/runtime.ts
|
|
19
19
|
import { randomUUID } from "crypto";
|
|
20
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
21
|
-
import {
|
|
20
|
+
import { mkdir, realpath, writeFile } from "fs/promises";
|
|
21
|
+
import { tmpdir } from "os";
|
|
22
|
+
import { join, resolve, sep } from "path";
|
|
22
23
|
import {
|
|
23
24
|
holoMailDefaults,
|
|
24
25
|
normalizeAppEnv
|
|
@@ -67,6 +68,10 @@ var mailRegistryInternals = {
|
|
|
67
68
|
|
|
68
69
|
// src/runtime.ts
|
|
69
70
|
var HOLO_MAIL_DELIVER_JOB = "holo.mail.deliver";
|
|
71
|
+
var LOCAL_ATTACHMENT_ROOTS = Object.freeze([
|
|
72
|
+
tmpdir(),
|
|
73
|
+
resolve(process.cwd(), "storage")
|
|
74
|
+
]);
|
|
70
75
|
var MailError = class extends Error {
|
|
71
76
|
code;
|
|
72
77
|
constructor(message, code = "MAIL_ERROR", options) {
|
|
@@ -267,7 +272,7 @@ function createPreviewHtml(preview) {
|
|
|
267
272
|
preview.cc.length > 0 ? `<p><strong>Cc:</strong> ${preview.cc.map(formatAddress).join(", ")}</p>` : "",
|
|
268
273
|
preview.bcc.length > 0 ? `<p><strong>Bcc:</strong> ${preview.bcc.map(formatAddress).join(", ")}</p>` : ""
|
|
269
274
|
].filter(Boolean).join("");
|
|
270
|
-
const body = preview.html ? preview.html : preview.text ? `<pre>${escapeHtml(preview.text)}</pre>` : "<p>No rendered content is available.</p>";
|
|
275
|
+
const body = preview.html ? `<pre>${escapeHtml(preview.html)}</pre>` : preview.text ? `<pre>${escapeHtml(preview.text)}</pre>` : "<p>No rendered content is available.</p>";
|
|
271
276
|
return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml(preview.subject)}</title></head><body>${header}${body}</body></html>`;
|
|
272
277
|
}
|
|
273
278
|
function escapeHtml(value) {
|
|
@@ -477,7 +482,7 @@ async function createSmtpAttachment(attachment) {
|
|
|
477
482
|
if (typeof attachment.path === "string") {
|
|
478
483
|
return {
|
|
479
484
|
...base,
|
|
480
|
-
path: attachment.path
|
|
485
|
+
path: await resolveLocalAttachmentPath(attachment.path)
|
|
481
486
|
};
|
|
482
487
|
}
|
|
483
488
|
if (attachment.storage) {
|
|
@@ -816,7 +821,29 @@ function stripMarkdownSyntax(markdown) {
|
|
|
816
821
|
return markdown.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)").replace(/`([^`]+)`/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/^[-*]\s+/gm, "").trim();
|
|
817
822
|
}
|
|
818
823
|
function renderMarkdownInline(markdown) {
|
|
819
|
-
return escapeHtml(markdown).replace(/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
824
|
+
return escapeHtml(markdown).replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, label, href) => {
|
|
825
|
+
return isSafeMailHref(href) ? `<a href="${escapeHtml(href)}">${label}</a>` : label;
|
|
826
|
+
}).replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
827
|
+
}
|
|
828
|
+
async function resolveLocalAttachmentPath(path) {
|
|
829
|
+
const resolvedPath = await realpath(path);
|
|
830
|
+
const allowedRoots = await Promise.all(LOCAL_ATTACHMENT_ROOTS.map(async (root) => await realpath(root).catch(() => null)));
|
|
831
|
+
if (allowedRoots.some((root) => root && (resolvedPath === root || resolvedPath.startsWith(`${root}${sep}`)))) {
|
|
832
|
+
return resolvedPath;
|
|
833
|
+
}
|
|
834
|
+
throw new MailError("[@holo-js/mail] Path attachments must resolve inside an allowed attachment directory.", "MAIL_ATTACHMENT_UNSAFE_PATH");
|
|
835
|
+
}
|
|
836
|
+
function isSafeMailHref(href) {
|
|
837
|
+
const trimmed = href.trim();
|
|
838
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("/") || trimmed.startsWith("./") || trimmed.startsWith("../")) {
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
try {
|
|
842
|
+
const url = new URL(trimmed);
|
|
843
|
+
return url.protocol === "https:" || url.protocol === "http:" || url.protocol === "mailto:";
|
|
844
|
+
} catch {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
820
847
|
}
|
|
821
848
|
function renderMarkdown(markdown) {
|
|
822
849
|
const blocks = markdown.trim().split(/\n\s*\n/g).map((block) => block.trim()).filter(Boolean);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holo-js/mail",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Holo-JS Framework - mail contracts, preview helpers, and fluent send helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"test": "vitest --run"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@holo-js/config": "^0.1.
|
|
31
|
+
"@holo-js/config": "^0.1.7",
|
|
32
32
|
"nodemailer": "^6.10.1"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@holo-js/queue": "^0.1.
|
|
36
|
-
"@holo-js/storage": "^0.1.
|
|
35
|
+
"@holo-js/queue": "^0.1.7",
|
|
36
|
+
"@holo-js/storage": "^0.1.7"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@holo-js/queue": {
|