mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
feat: Strip remote data from emails when viewed #54
This commit is contained in:
@@ -64,7 +64,8 @@ export interface AttachmentInfo {
|
||||
export interface MessageContentResponse {
|
||||
text?: string;
|
||||
html?: string;
|
||||
attachments?: AttachmentInfo[]
|
||||
attachments?: AttachmentInfo[];
|
||||
has_remote_content?: boolean;
|
||||
}
|
||||
|
||||
export interface NestedMessageContentResponse {
|
||||
@@ -72,6 +73,7 @@ export interface NestedMessageContentResponse {
|
||||
html?: string;
|
||||
attachments?: AttachmentInfo[];
|
||||
envelope: EmailEnvelope;
|
||||
has_remote_content?: boolean;
|
||||
}
|
||||
|
||||
export const getContent = (messageContent: MessageContentResponse): string | null => {
|
||||
@@ -83,13 +85,25 @@ export const getContent = (messageContent: MessageContentResponse): string | nul
|
||||
return null;
|
||||
};
|
||||
|
||||
export const load_message = async (accountId: number, id: string) => {
|
||||
const response = await axiosInstance.get<MessageContentResponse>(`api/v1/message-content/${accountId}/${id}`);
|
||||
export const load_message = async (accountId: number, id: string, blockRemoteContent = false) => {
|
||||
const params = new URLSearchParams();
|
||||
if (blockRemoteContent) {
|
||||
params.set('block_remote_content', 'true');
|
||||
}
|
||||
const qs = params.toString();
|
||||
const url = `api/v1/message-content/${accountId}/${id}${qs ? '?' + qs : ''}`;
|
||||
const response = await axiosInstance.get<MessageContentResponse>(url);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const load_nested_message = async (accountId: number, id: string, content_hash: string) => {
|
||||
const response = await axiosInstance.get<NestedMessageContentResponse>(`api/v1/nested-message-content/${accountId}/${id}?content_hash=${content_hash}`);
|
||||
export const load_nested_message = async (accountId: number, id: string, content_hash: string, blockRemoteContent = false) => {
|
||||
const params = new URLSearchParams({ content_hash });
|
||||
if (blockRemoteContent) {
|
||||
params.set('block_remote_content', 'true');
|
||||
}
|
||||
const response = await axiosInstance.get<NestedMessageContentResponse>(
|
||||
`api/v1/nested-message-content/${accountId}/${id}?${params.toString()}`
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ const EmailIframe: React.FC<EmailIframeProps> = ({ emailHtml, height }) => {
|
||||
return (
|
||||
<iframe
|
||||
src={iframeSrc}
|
||||
sandbox="allow-scripts"
|
||||
sandbox=""
|
||||
className="w-full border-none"
|
||||
title="Email Content"
|
||||
style={{ height: height ?? '4000px' }}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { Loader, Download, Trash2, MessageSquareMore, FileText, FileImage, FileVideo, FileArchive, FileSpreadsheet, FileCode, FileIcon, FileAudio, Upload } from 'lucide-react';
|
||||
import { Loader, Download, Trash2, MessageSquareMore, FileText, FileImage, FileVideo, FileArchive, FileSpreadsheet, FileCode, FileIcon, FileAudio, Upload, ShieldCheck } from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
@@ -121,6 +121,12 @@ export function MailMessageView({
|
||||
const [nestedEmlFile, setNestedEmlFile] = useState<AttachmentInfo | null>(null);
|
||||
const { getEmailById } = useMinimalAccountList();
|
||||
const [threadOpen, setThreadOpen] = useState(false);
|
||||
const [blockRemote, setBlockRemote] = useState(true);
|
||||
const [hasRemoteContent, setHasRemoteContent] = useState(false);
|
||||
|
||||
const toggleBlockRemote = () => {
|
||||
setBlockRemote((prev) => !prev);
|
||||
};
|
||||
|
||||
const downloadAttachmentMutation = useMutation({
|
||||
mutationFn: ({ content_hash }: { content_hash: string }) =>
|
||||
@@ -137,12 +143,13 @@ export function MailMessageView({
|
||||
});
|
||||
|
||||
const loadMessageMutation = useMutation({
|
||||
mutationFn: () => load_message(envelope.account_id, envelope.id),
|
||||
mutationFn: () => load_message(envelope.account_id, envelope.id, blockRemote),
|
||||
onSuccess: (data) => {
|
||||
setLoading(false);
|
||||
setContent(getContent(data));
|
||||
if (data.attachments) setAttachments(data.attachments);
|
||||
setContentType(data.html ? 'Html' : 'Plain');
|
||||
setHasRemoteContent(!!data.has_remote_content);
|
||||
},
|
||||
onError: (error: any) => {
|
||||
setLoading(false);
|
||||
@@ -154,10 +161,14 @@ export function MailMessageView({
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setBlockRemote(true);
|
||||
}, [envelope.id]);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
loadMessageMutation.mutate();
|
||||
}, [envelope.id]);
|
||||
}, [envelope.id, blockRemote]);
|
||||
|
||||
|
||||
const handleViewNestedEml = (attachment: AttachmentInfo) => {
|
||||
@@ -376,6 +387,30 @@ export function MailMessageView({
|
||||
</div>
|
||||
)}
|
||||
{showAttachments && <Separator className="mb-2" />}
|
||||
{hasRemoteContent && (
|
||||
<div className="flex items-center justify-between bg-muted border px-3 py-1.5 mb-3 text-xs">
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
<ShieldCheck className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||
{blockRemote ? (
|
||||
<span className="text-muted-foreground truncate">
|
||||
{t('mail.remoteBlocked', 'To protect your privacy, Bichon has blocked remote content in this message.')}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground truncate">
|
||||
{t('mail.remoteShown', 'Remote content is now shown.')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span
|
||||
className="underline cursor-pointer hover:no-underline text-muted-foreground text-[11px] font-medium shrink-0 ml-2 select-none"
|
||||
onClick={toggleBlockRemote}
|
||||
>
|
||||
{blockRemote
|
||||
? t('mail.showRemoteContent', 'Show remote content')
|
||||
: t('mail.blockRemoteAgain', 'Block again')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 overflow-auto">
|
||||
{loading ? (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
|
||||
@@ -150,7 +150,7 @@ export function NestedEmailDialog({ open, onOpenChange }: any) {
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['nested-message', currentAttachment?.account_id!, currentAttachment?.envelope_id!, currentAttachment?.content_hash!],
|
||||
queryFn: () => load_nested_message(currentAttachment?.account_id!, currentAttachment?.envelope_id!, currentAttachment?.content_hash!),
|
||||
queryFn: () => load_nested_message(currentAttachment?.account_id!, currentAttachment?.envelope_id!, currentAttachment?.content_hash!, true),
|
||||
enabled: open && !!currentAttachment,
|
||||
});
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { Loader, Download, Trash2, MessageSquareMore, FileText, FileImage, FileVideo, FileArchive, FileSpreadsheet, FileCode, FileIcon, FileAudio, Upload } from 'lucide-react';
|
||||
import { Loader, Download, Trash2, MessageSquareMore, FileText, FileImage, FileVideo, FileArchive, FileSpreadsheet, FileCode, FileIcon, FileAudio, Upload, ShieldCheck } from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
@@ -129,6 +129,12 @@ export function MailMessageView({
|
||||
const [nestedEmlFile, setNestedEmlFile] = useState<AttachmentInfo | null>(null);
|
||||
const { getEmailById } = useMinimalAccountList();
|
||||
const [threadOpen, setThreadOpen] = useState(false);
|
||||
const [blockRemote, setBlockRemote] = useState(true);
|
||||
const [hasRemoteContent, setHasRemoteContent] = useState(false);
|
||||
|
||||
const toggleBlockRemote = () => {
|
||||
setBlockRemote((prev) => !prev);
|
||||
};
|
||||
|
||||
const downloadAttachmentMutation = useMutation({
|
||||
mutationFn: ({ content_hash }: { content_hash: string }) =>
|
||||
@@ -145,12 +151,13 @@ export function MailMessageView({
|
||||
});
|
||||
|
||||
const loadMessageMutation = useMutation({
|
||||
mutationFn: () => load_message(envelope.account_id, envelope.id),
|
||||
mutationFn: () => load_message(envelope.account_id, envelope.id, blockRemote),
|
||||
onSuccess: (data) => {
|
||||
setLoading(false);
|
||||
setContent(getContent(data));
|
||||
if (data.attachments) setAttachments(data.attachments);
|
||||
setContentType(data.html ? 'Html' : 'Plain');
|
||||
setHasRemoteContent(!!data.has_remote_content);
|
||||
},
|
||||
onError: (error: any) => {
|
||||
setLoading(false);
|
||||
@@ -162,10 +169,14 @@ export function MailMessageView({
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setBlockRemote(true);
|
||||
}, [envelope.id]);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
loadMessageMutation.mutate();
|
||||
}, [envelope.id]);
|
||||
}, [envelope.id, blockRemote]);
|
||||
|
||||
|
||||
const handleViewNestedEml = (attachment: AttachmentInfo) => {
|
||||
@@ -384,6 +395,30 @@ export function MailMessageView({
|
||||
</div>
|
||||
)}
|
||||
{showAttachments && <Separator className="mb-2" />}
|
||||
{hasRemoteContent && (
|
||||
<div className="flex items-center justify-between bg-muted border px-3 py-1.5 mb-3 text-xs">
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
<ShieldCheck className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
||||
{blockRemote ? (
|
||||
<span className="text-muted-foreground truncate">
|
||||
{t('mail.remoteBlocked', 'To protect your privacy, Bichon has blocked remote content in this message.')}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground truncate">
|
||||
{t('mail.remoteShown', 'Remote content is now shown.')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span
|
||||
className="underline cursor-pointer hover:no-underline text-muted-foreground text-[11px] font-medium shrink-0 ml-2 select-none"
|
||||
onClick={toggleBlockRemote}
|
||||
>
|
||||
{blockRemote
|
||||
? t('mail.showRemoteContent', 'Show remote content')
|
||||
: t('mail.blockRemoteAgain', 'Block again')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 overflow-auto">
|
||||
{loading ? (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
|
||||
@@ -148,7 +148,7 @@ export function NestedEmailDialog({ open, onOpenChange, accountId, envelopeId, f
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['nested-message', accountId, envelopeId, content_hash],
|
||||
queryFn: () => load_nested_message(accountId, envelopeId, content_hash),
|
||||
queryFn: () => load_nested_message(accountId, envelopeId, content_hash, true),
|
||||
enabled: open && !!content_hash,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user