diff --git a/components/identity/identity-form.tsx b/components/identity/identity-form.tsx index 71cd5313..1dbefb65 100644 --- a/components/identity/identity-form.tsx +++ b/components/identity/identity-form.tsx @@ -8,6 +8,31 @@ import type { Identity, EmailAddress } from '@/lib/jmap/types'; import { sanitizeSignatureHtml } from '@/lib/email-sanitization'; import { getEmailValidationError, validateEmailList } from '@/lib/validation'; +// JMAP Identity/set caps signature fields at 2047 UTF-8 bytes per RFC 8621 §6.1. +const SIGNATURE_MAX_BYTES = 2047; +const utf8Encoder = new TextEncoder(); + +function utf8ByteLength(s: string): number { + return utf8Encoder.encode(s).length; +} + +function truncateToUtf8Bytes(s: string, maxBytes: number): string { + if (utf8ByteLength(s) <= maxBytes) return s; + let lo = 0; + let hi = s.length; + while (lo < hi) { + const mid = (lo + hi + 1) >>> 1; + if (utf8ByteLength(s.slice(0, mid)) <= maxBytes) lo = mid; + else hi = mid - 1; + } + // Don't split a surrogate pair: if we landed right after a high surrogate, back off one code unit. + if (lo > 0) { + const prev = s.charCodeAt(lo - 1); + if (prev >= 0xD800 && prev <= 0xDBFF) lo -= 1; + } + return s.slice(0, lo); +} + interface IdentityFormData { name: string; email: string; @@ -246,14 +271,15 @@ export function IdentityForm({ identity, onSave, onCancel }: IdentityFormProps)