feat: reader-rendering correctness + privacy hardening (P1·S batch)
Close the five open P1·S items from TODO.md: - X-Robots-Tag: noindex on rss/atom/entries/files + a /robots.txt - absolutize relative content URLs against the sender's site - promote lazy-loaded images (data-src → src, strip loading="lazy") - strip XML-illegal control chars from generated feeds (keep emoji) - plain-text feed <title> (strip HTML, decode entities) Sender-base derivation lives on the EmailAddress value object (siteBaseUrl) instead of a misplaced favicon helper. Bump to 0.2.1 and document the changes in README + CLAUDE.md. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
81e46c9026
commit
97ce9a62b4
20 files changed
+414
-29
No files matched your search
@@ -313,6 +313,66 @@ describe("generateAtomFeed", () => {
|
||||
expect(result).toContain("Bob");
|
||||
});
|
||||
|
||||
it("renders the subject as plain text in <title> (strips tags, decodes entities)", () => {
|
||||
const emailWithHtmlSubject: EmailData = {
|
||||
...mockEmails[0],
|
||||
subject: "<b>Sale</b> Tom & Jerry",
|
||||
};
|
||||
const result = generateAtomFeed(
|
||||
mockFeedConfig,
|
||||
[emailWithHtmlSubject],
|
||||
BASE_URL,
|
||||
FEED_ID,
|
||||
);
|
||||
// Tags are stripped and entities decoded; markup must not survive.
|
||||
expect(result).toContain("Sale Tom & Jerry");
|
||||
expect(result).not.toContain("<b>Sale</b>");
|
||||
});
|
||||
|
||||
it("strips XML-illegal control characters from the output", () => {
|
||||
const emailWithControlChar: EmailData = {
|
||||
...mockEmails[0],
|
||||
subject: "Bad\x00\x1Fchar",
|
||||
content: "<p>body\x0Bhere</p>",
|
||||
};
|
||||
const result = generateAtomFeed(
|
||||
mockFeedConfig,
|
||||
[emailWithControlChar],
|
||||
BASE_URL,
|
||||
FEED_ID,
|
||||
);
|
||||
expect(result).not.toMatch(/[\x00\x0B\x1F]/);
|
||||
});
|
||||
|
||||
it("preserves emoji (surrogate pairs) in the output", () => {
|
||||
const emailWithEmoji: EmailData = {
|
||||
...mockEmails[0],
|
||||
subject: "Launch 🚀 today",
|
||||
};
|
||||
const result = generateAtomFeed(
|
||||
mockFeedConfig,
|
||||
[emailWithEmoji],
|
||||
BASE_URL,
|
||||
FEED_ID,
|
||||
);
|
||||
expect(result).toContain("🚀");
|
||||
});
|
||||
|
||||
it("absolutizes relative content URLs against the sender domain", () => {
|
||||
const emailWithRelative: EmailData = {
|
||||
...mockEmails[0],
|
||||
from: "News <[email protected]>",
|
||||
content: '<body><a href="/article">read</a></body>',
|
||||
};
|
||||
const result = generateAtomFeed(
|
||||
mockFeedConfig,
|
||||
[emailWithRelative],
|
||||
BASE_URL,
|
||||
FEED_ID,
|
||||
);
|
||||
expect(result).toContain("https://acme.com/article");
|
||||
});
|
||||
|
||||
it("includes enclosure link for email with attachment in Atom feed", () => {
|
||||
const result = generateAtomFeed(
|
||||
mockFeedConfig,
|
||||
|
||||
Reference in new issue
Block a user