Replying sanitized the quoted body with allowRemote: true, so quoting fetched every remote image in the message whatever the reader had decided about it. A tracking pixel in the quote then reported the message read, and the address live, to whoever was counting -- the thing leaving the images blocked was meant to prevent. Edit as new and opening a draft that quotes a message did the same. The decision now lives in one place, remoteImagesAllowed(), asked with the same inputs the reader's answer used: the image policy, the trusted senders, whether the sender is a contact, and whether Show images was pressed on that message. The last of those was component state, so it moves to the mail store, where the composer can see it. Blocked images already keep their address in data-ihm-remote, so nothing is lost by not fetching: it goes back on the way out, and the sent quote is what its sender wrote. The recipient's client decides for itself, as it would with any other client's reply. Before pr408 this needed a rich-text default to reach; the format offer made it reachable from plain text, which is how it was found. No new strings. (cherry picked from commit d329b33912921a851c548bffef5085e5fbf72bed)
This commit is contained in:
1 parent
9ba2c6e290
commit
a94fd9cce3
6 files changed
+193
-6
No files matched your search
@@ -0,0 +1,45 @@
|
||||
import type { ImagePolicy } from "@/store/settings";
|
||||
|
||||
/**
|
||||
* Whether a message's remote images may be fetched.
|
||||
*
|
||||
* The reader's decision, in one place, because the composer has to make the
|
||||
* same one. Quoting a message into a reply renders it again — and a quote that
|
||||
* fetched what the reader had declined would report the message read, and the
|
||||
* address live, to whoever was counting. The tracking pixel does not care
|
||||
* which window it loaded in.
|
||||
*/
|
||||
export function remoteImagesAllowed(opts: {
|
||||
from: string | null | undefined;
|
||||
policy: ImagePolicy;
|
||||
trusted: string[];
|
||||
inContacts: boolean;
|
||||
/** The reader pressed "Show images" on this message. */
|
||||
shown: boolean;
|
||||
}): boolean {
|
||||
if (opts.shown || opts.policy === "always") return true;
|
||||
if (opts.trusted.includes((opts.from ?? "").toLowerCase())) return true;
|
||||
return opts.policy === "contacts" && opts.inContacts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put back the addresses of images that were blocked when the message was
|
||||
* quoted, on the way out.
|
||||
*
|
||||
* Blocking keeps the original URL on the element (`data-ihm-remote`), so
|
||||
* nothing was lost by not fetching it. The copy that leaves here should be the
|
||||
* quote as its sender wrote it: the recipient's client decides for itself
|
||||
* whether to load those images, the same as it would have with any other
|
||||
* client's reply.
|
||||
*/
|
||||
export function restoreBlockedImages(html: string): string {
|
||||
if (!html.includes("data-ihm-blocked")) return html;
|
||||
const doc = new DOMParser().parseFromString(html, "text/html");
|
||||
for (const img of Array.from(doc.querySelectorAll("img[data-ihm-blocked]"))) {
|
||||
const url = img.getAttribute("data-ihm-remote");
|
||||
if (url) img.setAttribute("src", url);
|
||||
img.removeAttribute("data-ihm-blocked");
|
||||
img.removeAttribute("data-ihm-remote");
|
||||
}
|
||||
return doc.body.innerHTML;
|
||||
}
|
||||
Reference in new issue
Block a user