mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-03 00:06:39 +00:00
fix: send to addresses on internationalized domains (bulwarkmail/webmail#1100)
This commit is contained in:
@@ -11,6 +11,7 @@ import type { Identity } from '../api/types';
|
||||
import { useLocaleStore } from '../stores/locale-store';
|
||||
import { MODAL_ORIENTATIONS } from '../lib/modal-orientations';
|
||||
import { useSheetInsets } from '../lib/use-layout';
|
||||
import { toUnicodeEmail } from '../lib/idn';
|
||||
|
||||
interface IdentitySheetProps {
|
||||
visible: boolean;
|
||||
@@ -97,7 +98,7 @@ export function IdentitySheet({
|
||||
</Text>
|
||||
)}
|
||||
<Text style={[styles.identityEmail, !identity.name && styles.identityEmailOnly]} numberOfLines={1}>
|
||||
{identity.email}
|
||||
{toUnicodeEmail(identity.email)}
|
||||
</Text>
|
||||
</View>
|
||||
{isSelected && (
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
// Addresses on internationalized domains (webmail #1100): the composer sends
|
||||
// a domain typed in Unicode in its ASCII form, shows the From identity as
|
||||
// written, and a sender on one of the user's own IDN domains reads as
|
||||
// written in the reader. No React Native renderer in the test env, so this
|
||||
// checks the wiring; outgoingAddress and toUnicodeEmailOnOwnDomains have unit
|
||||
// tests in recipients.test.ts and idn.test.ts.
|
||||
|
||||
const read = (rel: string) => fs.readFileSync(path.resolve(__dirname, '..', '..', rel), 'utf8');
|
||||
|
||||
describe('IDN addresses in the composer and reader', () => {
|
||||
it('builds the outgoing To, Cc and Bcc through outgoingAddress', () => {
|
||||
const src = read('screens/ComposeScreen.tsx');
|
||||
expect(src).toMatch(/function toAddress\(r: \{ name\?: string; email: string \}\): EmailAddress \{\s*return outgoingAddress\(r\);/);
|
||||
expect(src).toMatch(/to: expandRecipients\(finalTo\)\.map\(toAddress\)/);
|
||||
});
|
||||
|
||||
it('shows the From identity with its domain as written', () => {
|
||||
expect(read('screens/ComposeScreen.tsx')).toMatch(/const email = toUnicodeEmail\(from\.email\);/);
|
||||
expect(read('components/IdentitySheet.tsx')).toMatch(/\{toUnicodeEmail\(identity\.email\)\}/);
|
||||
});
|
||||
|
||||
it('formats the sender address in the reader through useOwnDomainAddress', () => {
|
||||
const header = read('components/email/MessageHeader.tsx');
|
||||
expect(header).toMatch(/const fromEmail = from\?\.email \? formatAddress\(from\.email\) : '';/);
|
||||
expect(header).toMatch(/\{from\?\.name \|\| fromEmail \|\| t\('email_viewer\.unknown_sender'/);
|
||||
expect(header).not.toMatch(/\{from\.email\}<\/Text>/);
|
||||
const card = read('components/email/ThreadMessageCard.tsx');
|
||||
expect(card).toMatch(/\{from\?\.name \|\| \(from\?\.email && formatAddress\(from\.email\)\) \|\|/);
|
||||
});
|
||||
});
|
||||
@@ -21,6 +21,7 @@ import { isSmimeEmail } from '../../lib/smime';
|
||||
import { resolveEmailTags } from '../../lib/thread-utils';
|
||||
import { findVerificationCode, verificationCodeBodyText } from '../../lib/verification-code';
|
||||
import { VerificationCodeChip } from './VerificationCodeChip';
|
||||
import { useOwnDomainAddress } from '../../lib/use-own-domain-address';
|
||||
|
||||
interface Props {
|
||||
email: Email;
|
||||
@@ -109,6 +110,9 @@ export function MessageHeader({ email, identities, headerInfo, onToggleStar, onA
|
||||
|
||||
const info = React.useMemo(() => headerInfo ?? deriveHeaderInfo(email), [headerInfo, email]);
|
||||
const from = email.from?.[0];
|
||||
// An address on one of the user's own IDN domains reads as written (#1100).
|
||||
const formatAddress = useOwnDomainAddress();
|
||||
const fromEmail = from?.email ? formatAddress(from.email) : '';
|
||||
const starred = !!email.keywords?.$flagged;
|
||||
const displayDate = emailDisplayDate(email);
|
||||
const spoofed = isAuthenticationSpoofed(info.auth);
|
||||
@@ -170,7 +174,7 @@ export function MessageHeader({ email, identities, headerInfo, onToggleStar, onA
|
||||
onPress={from ? () => onAddressPress(from) : undefined}
|
||||
hitSlop={4}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={from?.name || from?.email || t('email_viewer.unknown_sender', 'Unknown')}
|
||||
accessibilityLabel={from?.name || fromEmail || t('email_viewer.unknown_sender', 'Unknown')}
|
||||
>
|
||||
<SenderAvatar
|
||||
name={from?.name}
|
||||
@@ -182,10 +186,10 @@ export function MessageHeader({ email, identities, headerInfo, onToggleStar, onA
|
||||
<View style={styles.info}>
|
||||
<Pressable onPress={from ? () => onAddressPress(from) : undefined}>
|
||||
<Text style={styles.name} numberOfLines={1}>
|
||||
{from?.name || from?.email || t('email_viewer.unknown_sender', 'Unknown')}
|
||||
{from?.name || fromEmail || t('email_viewer.unknown_sender', 'Unknown')}
|
||||
</Text>
|
||||
{from?.name && from?.email ? (
|
||||
<Text style={styles.email} numberOfLines={1}>{from.email}</Text>
|
||||
{from?.name && fromEmail ? (
|
||||
<Text style={styles.email} numberOfLines={1}>{fromEmail}</Text>
|
||||
) : null}
|
||||
</Pressable>
|
||||
{verificationText && (
|
||||
|
||||
@@ -12,6 +12,7 @@ import { emailDisplayDate, formatHeaderDate, formatHeaderTime } from '../../lib/
|
||||
import { singleLine } from '../../lib/single-line';
|
||||
import { cleanPreview } from '../../lib/preview-text';
|
||||
import { useSenderWarning } from './use-sender-warning';
|
||||
import { useOwnDomainAddress } from '../../lib/use-own-domain-address';
|
||||
|
||||
interface Props extends MessageContentProps {
|
||||
expanded: boolean;
|
||||
@@ -46,6 +47,8 @@ export function ThreadMessageCard({
|
||||
const starred = !!email.keywords?.$flagged;
|
||||
const date = emailDisplayDate(email);
|
||||
const senderWarning = useSenderWarning(expanded ? undefined : email);
|
||||
// An address on one of the user's own IDN domains reads as written (#1100).
|
||||
const formatAddress = useOwnDomainAddress();
|
||||
|
||||
if (!expanded) {
|
||||
return (
|
||||
@@ -54,7 +57,7 @@ export function ThreadMessageCard({
|
||||
<View style={styles.collapsedInfo}>
|
||||
<View style={styles.collapsedTop}>
|
||||
<Text style={[styles.collapsedName, unread && styles.unread]} numberOfLines={1}>
|
||||
{from?.name || from?.email || t('email_viewer.unknown_sender', 'Unknown')}
|
||||
{from?.name || (from?.email && formatAddress(from.email)) || t('email_viewer.unknown_sender', 'Unknown')}
|
||||
</Text>
|
||||
{email.hasAttachment && <Paperclip size={12} color={c.textMuted} />}
|
||||
<Text style={styles.collapsedDate}>{formatHeaderDate(date, locale)} {formatHeaderTime(date, timeFormat, locale)}</Text>
|
||||
|
||||
Reference in New Issue
Block a user