mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: stop sending a contact group whose member is not an address #795
This commit is contained in:
@@ -256,6 +256,19 @@ describe('composer recipient validation', () => {
|
||||
expect(toastError).toHaveBeenCalledWith('validation.invalid_recipients');
|
||||
});
|
||||
|
||||
it('does not send a group whose member is not an address and names the member', async () => {
|
||||
const onSend = vi.fn();
|
||||
render(<EmailComposer initialData={{ ...DRAFT, to: 'Team: bob@example.com, junk;' }} onSend={onSend} />);
|
||||
|
||||
expect(screen.getByText('Team (2)').closest('[data-invalid]')).not.toBeNull();
|
||||
|
||||
fireEvent.click(sendButton());
|
||||
await act(async () => {});
|
||||
|
||||
expect(onSend).not.toHaveBeenCalled();
|
||||
expect(toastError).toHaveBeenCalledWith('validation.invalid_recipients');
|
||||
});
|
||||
|
||||
it('sends when every recipient is an address', async () => {
|
||||
const onSend = vi.fn();
|
||||
render(<EmailComposer initialData={{ ...DRAFT, to: 'bob@example.com' }} onSend={onSend} />);
|
||||
|
||||
@@ -105,6 +105,7 @@ import {
|
||||
formatRecipientList,
|
||||
expandRecipients,
|
||||
splitPastedRecipients,
|
||||
invalidRecipientAddresses,
|
||||
defaultSuggestionIndex,
|
||||
waitForPendingUploads,
|
||||
extractUserAuthoredText,
|
||||
@@ -2881,11 +2882,11 @@ export function EmailComposer({
|
||||
|
||||
// Catch addresses the server would reject before asking it. Its answer
|
||||
// ("No recipients found in email") does not say which one is wrong.
|
||||
const invalidRecipients = [
|
||||
const invalidRecipients = invalidRecipientAddresses([
|
||||
...withInput(to, toInput),
|
||||
...withInput(cc, ccInput),
|
||||
...withInput(bcc, bccInput),
|
||||
].filter(r => !r.group && !isValidEmail(r.email)).map(r => r.email);
|
||||
]);
|
||||
if (invalidRecipients.length > 0) {
|
||||
toast.error(t('validation.invalid_recipients', { addresses: invalidRecipients.join(', ') }));
|
||||
return;
|
||||
@@ -4999,7 +5000,7 @@ function RecipientChipInput({
|
||||
if(chip.extra?.icon){
|
||||
IconComponent = ICON_MAP[chip.extra?.icon];
|
||||
}
|
||||
const isInvalid = !chip.group && !isValidEmail(chip.email);
|
||||
const isInvalid = invalidRecipientAddresses([chip]).length > 0;
|
||||
const customColor = isInvalid ? 'destructive' : chip.extra?.color;
|
||||
|
||||
return (
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
extractUserAuthoredText,
|
||||
formatRecipientEntry,
|
||||
expandRecipients,
|
||||
invalidRecipientAddresses,
|
||||
getQuoteBodies,
|
||||
} from '../email-composer-utils';
|
||||
|
||||
@@ -594,6 +595,16 @@ describe('contact group recipients (RFC 5322 group syntax)', () => {
|
||||
expect(parsed[1].group?.members).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('keeps typed text with a colon as a plain recipient when no member is an address', () => {
|
||||
expect(parseRecipient('Subject: hello;')).toEqual({ email: 'Subject: hello;' });
|
||||
});
|
||||
|
||||
it('lists group members that are not addresses as invalid', () => {
|
||||
const parsed = parseRecipientList('ok@x.de, Team: a@x.de, junk;, nope');
|
||||
expect(invalidRecipientAddresses(parsed)).toEqual(['junk', 'nope']);
|
||||
expect(invalidRecipientAddresses([group])).toEqual([]);
|
||||
});
|
||||
|
||||
it('expandRecipients flattens groups and dedupes against individuals', () => {
|
||||
const expanded = expandRecipients([
|
||||
{ name: 'Anna Alt', email: 'ANNA@example.com' },
|
||||
|
||||
@@ -383,9 +383,9 @@ export function parseRecipient(s: string): Recipient {
|
||||
const members = splitRecipients(trimmed.slice(colon + 1, -1))
|
||||
.map(parseRecipient)
|
||||
.filter((m) => m.email && !m.group);
|
||||
// Only accept the group form when it actually carries members - typed
|
||||
// garbage like "Subject: hello;" stays a plain (invalid) recipient.
|
||||
if (members.length > 0) {
|
||||
// Only accept the group form when at least one member is an address -
|
||||
// typed garbage like "Subject: hello;" stays a plain (invalid) recipient.
|
||||
if (members.some((m) => isValidEmail(m.email))) {
|
||||
return { name: unquoteName(trimmed.slice(0, colon)), email: '', group: { members } };
|
||||
}
|
||||
}
|
||||
@@ -462,6 +462,18 @@ export function expandRecipients(recipients: Recipient[]): Recipient[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* The entries of `recipients` that are not email addresses, group members
|
||||
* included. Sending any of them puts a malformed mailbox into the header,
|
||||
* which relays reject only after the whole message went through (#795).
|
||||
*/
|
||||
export function invalidRecipientAddresses(recipients: Recipient[]): string[] {
|
||||
return recipients
|
||||
.flatMap((r) => (r.group ? r.group.members : [r]))
|
||||
.filter((r) => !isValidEmail(r.email))
|
||||
.map((r) => r.email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Top-level split of a pasted block into recipient entries on commas,
|
||||
* semicolons and newlines (separators inside a quoted name or angle brackets
|
||||
|
||||
Reference in New Issue
Block a user