Settings > General gains a "Default mail app" section that calls registerProtocolHandler so mail links anywhere in the browser open ihasmail. The browser owns the decision and there is no API to read it back, so the UI says what it can: it records that we asked, offers "Ask again", shows a Remove button where unregisterProtocolHandler exists, and points at the browser's own settings. Unsupported browsers (Safari) and insecure contexts get an explanation instead of a dead button. The manifest now declares protocol_handlers for mailto, which is the route by which an *installed* app can be offered by the operating system itself; the UI says so and links the two ideas rather than promising a system-wide default the page cannot grant. Mailto parsing is now one function (parseMailto in lib/address.ts) instead of three hand-rolled copies in AppShell and MessageView. It follows RFC 6068: recipients from the path, the to= header or both, case-insensitive headers, "+" as space, and tolerant of malformed escapes. That fixes Cc and Bcc being silently dropped, and draftFromMailto escapes the body so a mailto: URL from an untrusted page reaches the composer as text rather than markup.
36 lines
1.4 KiB
TypeScript
36 lines
1.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { draftFromMailto } from "@/store/compose";
|
|
|
|
/**
|
|
* mailto: URLs arrive from anywhere — a web page, a document, another app —
|
|
* so the body must reach the composer as text, never as markup.
|
|
*/
|
|
describe("draftFromMailto", () => {
|
|
it("fills recipients, subject and body", () => {
|
|
const d = draftFromMailto("mailto:[email protected][email protected]&[email protected]&subject=Q3%20plan&body=Hi%20Ann");
|
|
expect(d.to).toEqual([{ name: null, email: "[email protected]" }]);
|
|
expect(d.cc).toEqual([{ name: null, email: "[email protected]" }]);
|
|
expect(d.bcc).toEqual([{ name: null, email: "[email protected]" }]);
|
|
expect(d.showCc).toBe(true);
|
|
expect(d.showBcc).toBe(true);
|
|
expect(d.subject).toBe("Q3 plan");
|
|
expect(d.text).toBe("Hi Ann");
|
|
});
|
|
|
|
it("escapes markup in the body and keeps line breaks", () => {
|
|
const d = draftFromMailto("mailto:[email protected]?body=%3Cimg%20src%3Dx%20onerror%3Dboom%3E%20%26%20plain%0Asecond%20line");
|
|
expect(d.html).not.toContain("<img");
|
|
expect(d.html).toContain("<img");
|
|
expect(d.html).toContain("&");
|
|
expect(d.html).toContain("<br>");
|
|
expect(d.text).toContain("<img");
|
|
});
|
|
|
|
it("leaves the body alone when the URL has none", () => {
|
|
const d = draftFromMailto("mailto:[email protected]");
|
|
expect(d.html).toBeUndefined();
|
|
expect(d.text).toBeUndefined();
|
|
expect(d.showCc).toBe(false);
|
|
});
|
|
});
|