1use std::{
8 borrow::Cow,
9 cmp::Ordering,
10 fmt,
11 hash::{Hash, Hasher},
12};
13
14macro_rules! header_names {
15 ($($variant:ident, $name:literal, $lc:literal;)+) => {
16 #[derive(Debug, Clone)]
38 #[non_exhaustive]
39 pub enum HeaderName<'x> {
40 $(
41 #[doc = concat!("`", $name, "`.")]
42 $variant,
43 )+
44 Other(Cow<'x, str>),
46 }
47
48 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
49 #[repr(u16)]
50 pub(crate) enum HeaderId {
51 $($variant,)+
52 }
53
54 pub(crate) const KNOWN_NAMES: usize = [$($lc,)+].len();
55
56 static CANONICAL: [&str; KNOWN_NAMES] = [$($name,)+];
57
58 const LOWERCASE: [&str; KNOWN_NAMES] = [$($lc,)+];
59
60 static VARIANTS: [HeaderName<'static>; KNOWN_NAMES] = [$(HeaderName::$variant,)+];
61
62 impl HeaderName<'_> {
63 pub(crate) fn id(&self) -> u16 {
64 match self {
65 $(HeaderName::$variant => HeaderId::$variant as u16,)+
66 HeaderName::Other(_) => OTHER_ID,
67 }
68 }
69 }
70
71 pub(crate) fn lookup_lowercase(name: &[u8]) -> Option<u16> {
72 hashify::fnc_map!(name,
73 $($lc => Some(HeaderId::$variant as u16),)+
74 _ => None
75 )
76 }
77 };
78}
79
80pub(crate) const OTHER_ID: u16 = u16::MAX;
81const CONTENT_PREFIX: &[u8] = b"content-";
82pub(crate) const FIRST_VENDOR: u16 = HeaderId::XOriginalTo as u16;
83
84header_names! {
85 Subject, "Subject", "subject";
86 From, "From", "from";
87 To, "To", "to";
88 Cc, "Cc", "cc";
89 Date, "Date", "date";
90 Bcc, "Bcc", "bcc";
91 ReplyTo, "Reply-To", "reply-to";
92 Sender, "Sender", "sender";
93 Comments, "Comments", "comments";
94 InReplyTo, "In-Reply-To", "in-reply-to";
95 Keywords, "Keywords", "keywords";
96 Received, "Received", "received";
97 MessageId, "Message-ID", "message-id";
98 References, "References", "references";
99 ReturnPath, "Return-Path", "return-path";
100 MimeVersion, "MIME-Version", "mime-version";
101 ContentDescription, "Content-Description", "content-description";
102 ContentId, "Content-ID", "content-id";
103 ContentLanguage, "Content-Language", "content-language";
104 ContentLocation, "Content-Location", "content-location";
105 ContentTransferEncoding, "Content-Transfer-Encoding", "content-transfer-encoding";
106 ContentType, "Content-Type", "content-type";
107 ContentDisposition, "Content-Disposition", "content-disposition";
108 ResentTo, "Resent-To", "resent-to";
109 ResentFrom, "Resent-From", "resent-from";
110 ResentBcc, "Resent-Bcc", "resent-bcc";
111 ResentCc, "Resent-Cc", "resent-cc";
112 ResentSender, "Resent-Sender", "resent-sender";
113 ResentDate, "Resent-Date", "resent-date";
114 ResentMessageId, "Resent-Message-ID", "resent-message-id";
115 ListArchive, "List-Archive", "list-archive";
116 ListHelp, "List-Help", "list-help";
117 ListId, "List-ID", "list-id";
118 ListOwner, "List-Owner", "list-owner";
119 ListPost, "List-Post", "list-post";
120 ListSubscribe, "List-Subscribe", "list-subscribe";
121 ListUnsubscribe, "List-Unsubscribe", "list-unsubscribe";
122 DkimSignature, "DKIM-Signature", "dkim-signature";
123 ArcAuthenticationResults, "ARC-Authentication-Results", "arc-authentication-results";
124 ArcMessageSignature, "ARC-Message-Signature", "arc-message-signature";
125 ArcSeal, "ARC-Seal", "arc-seal";
126 Dkim2Signature, "DKIM2-Signature", "dkim2-signature";
127 MessageInstance, "Message-Instance", "message-instance";
128 AcceptLanguage, "Accept-Language", "accept-language";
129 AlternateRecipient, "Alternate-Recipient", "alternate-recipient";
130 ArchivedAt, "Archived-At", "archived-at";
131 AuthenticationResults, "Authentication-Results", "authentication-results";
132 AutoSubmitted, "Auto-Submitted", "auto-submitted";
133 Autoforwarded, "Autoforwarded", "autoforwarded";
134 Autosubmitted, "Autosubmitted", "autosubmitted";
135 ContentAlternative, "Content-Alternative", "content-alternative";
136 ContentDuration, "Content-Duration", "content-duration";
137 ContentFeatures, "Content-features", "content-features";
138 ContentMd5, "Content-MD5", "content-md5";
139 ContentTranslationType, "Content-Translation-Type", "content-translation-type";
140 Conversion, "Conversion", "conversion";
141 ConversionWithLoss, "Conversion-With-Loss", "conversion-with-loss";
142 DlExpansionHistory, "DL-Expansion-History", "dl-expansion-history";
143 DeferredDelivery, "Deferred-Delivery", "deferred-delivery";
144 DeliveryDate, "Delivery-Date", "delivery-date";
145 DiscardedX400IpmsExtensions, "Discarded-X400-IPMS-Extensions", "discarded-x400-ipms-extensions";
146 DiscardedX400MtsExtensions, "Discarded-X400-MTS-Extensions", "discarded-x400-mts-extensions";
147 DiscloseRecipients, "Disclose-Recipients", "disclose-recipients";
148 DispositionNotificationOptions, "Disposition-Notification-Options", "disposition-notification-options";
149 DispositionNotificationTo, "Disposition-Notification-To", "disposition-notification-to";
150 DowngradedFinalRecipient, "Downgraded-Final-Recipient", "downgraded-final-recipient";
151 DowngradedInReplyTo, "Downgraded-In-Reply-To", "downgraded-in-reply-to";
152 DowngradedMessageId, "Downgraded-Message-Id", "downgraded-message-id";
153 DowngradedOriginalRecipient, "Downgraded-Original-Recipient", "downgraded-original-recipient";
154 DowngradedReferences, "Downgraded-References", "downgraded-references";
155 Encoding, "Encoding", "encoding";
156 Expires, "Expires", "expires";
157 GenerateDeliveryReport, "Generate-Delivery-Report", "generate-delivery-report";
158 HpOuter, "HP-Outer", "hp-outer";
159 Importance, "Importance", "importance";
160 IncompleteCopy, "Incomplete-Copy", "incomplete-copy";
161 Language, "Language", "language";
162 LatestDeliveryTime, "Latest-Delivery-Time", "latest-delivery-time";
163 ListUnsubscribePost, "List-Unsubscribe-Post", "list-unsubscribe-post";
164 MessageContext, "Message-Context", "message-context";
165 MessageType, "Message-Type", "message-type";
166 MmhsExemptedAddress, "MMHS-Exempted-Address", "mmhs-exempted-address";
167 MmhsExtendedAuthorisationInfo, "MMHS-Extended-Authorisation-Info", "mmhs-extended-authorisation-info";
168 MmhsSubjectIndicatorCodes, "MMHS-Subject-Indicator-Codes", "mmhs-subject-indicator-codes";
169 MmhsHandlingInstructions, "MMHS-Handling-Instructions", "mmhs-handling-instructions";
170 MmhsMessageInstructions, "MMHS-Message-Instructions", "mmhs-message-instructions";
171 MmhsCodressMessageIndicator, "MMHS-Codress-Message-Indicator", "mmhs-codress-message-indicator";
172 MmhsOriginatorReference, "MMHS-Originator-Reference", "mmhs-originator-reference";
173 MmhsPrimaryPrecedence, "MMHS-Primary-Precedence", "mmhs-primary-precedence";
174 MmhsCopyPrecedence, "MMHS-Copy-Precedence", "mmhs-copy-precedence";
175 MmhsMessageType, "MMHS-Message-Type", "mmhs-message-type";
176 MmhsOtherRecipientsIndicatorTo, "MMHS-Other-Recipients-Indicator-To", "mmhs-other-recipients-indicator-to";
177 MmhsOtherRecipientsIndicatorCc, "MMHS-Other-Recipients-Indicator-CC", "mmhs-other-recipients-indicator-cc";
178 MmhsAcp127MessageIdentifier, "MMHS-Acp127-Message-Identifier", "mmhs-acp127-message-identifier";
179 MmhsOriginatorPlad, "MMHS-Originator-PLAD", "mmhs-originator-plad";
180 MtPriority, "MT-Priority", "mt-priority";
181 Organization, "Organization", "organization";
182 OriginalEncodedInformationTypes, "Original-Encoded-Information-Types", "original-encoded-information-types";
183 OriginalFrom, "Original-From", "original-from";
184 OriginalMessageId, "Original-Message-ID", "original-message-id";
185 OriginalRecipient, "Original-Recipient", "original-recipient";
186 OriginatorReturnAddress, "Originator-Return-Address", "originator-return-address";
187 OriginalSubject, "Original-Subject", "original-subject";
188 PicsLabel, "PICS-Label", "pics-label";
189 PreventNonDeliveryReport, "Prevent-NonDelivery-Report", "prevent-nondelivery-report";
190 Priority, "Priority", "priority";
191 ReceivedSpf, "Received-SPF", "received-spf";
192 ReplyBy, "Reply-By", "reply-by";
193 RequireRecipientValidSince, "Require-Recipient-Valid-Since", "require-recipient-valid-since";
194 Sensitivity, "Sensitivity", "sensitivity";
195 Solicitation, "Solicitation", "solicitation";
196 Supersedes, "Supersedes", "supersedes";
197 TlsReportDomain, "TLS-Report-Domain", "tls-report-domain";
198 TlsReportSubmitter, "TLS-Report-Submitter", "tls-report-submitter";
199 TlsRequired, "TLS-Required", "tls-required";
200 VbrInfo, "VBR-Info", "vbr-info";
201 X400ContentIdentifier, "X400-Content-Identifier", "x400-content-identifier";
202 X400ContentReturn, "X400-Content-Return", "x400-content-return";
203 X400ContentType, "X400-Content-Type", "x400-content-type";
204 X400MtsIdentifier, "X400-MTS-Identifier", "x400-mts-identifier";
205 X400Originator, "X400-Originator", "x400-originator";
206 X400Received, "X400-Received", "x400-received";
207 X400Recipients, "X400-Recipients", "x400-recipients";
208 X400Trace, "X400-Trace", "x400-trace";
209 ApparentlyTo, "Apparently-To", "apparently-to";
210 Author, "Author", "author";
211 CfblAddress, "CFBL-Address", "cfbl-address";
212 CfblFeedbackId, "CFBL-Feedback-ID", "cfbl-feedback-id";
213 DeliveredTo, "Delivered-To", "delivered-to";
214 EdiintFeatures, "EDIINT-Features", "ediint-features";
215 EesstVersion, "Eesst-Version", "eesst-version";
216 ErrorsTo, "Errors-To", "errors-to";
217 Face, "Face", "face";
218 FormSub, "Form-Sub", "form-sub";
219 JabberId, "Jabber-ID", "jabber-id";
220 MmhsAuthorizingUsers, "MMHS-Authorizing-Users", "mmhs-authorizing-users";
221 Privicon, "Privicon", "privicon";
222 SioLabel, "SIO-Label", "sio-label";
223 SioLabelHistory, "SIO-Label-History", "sio-label-history";
224 WrongRecipient, "Wrong-Recipient", "wrong-recipient";
225 XOriginalTo, "X-Original-To", "x-original-to";
226 ReturnReceiptTo, "Return-Receipt-To", "return-receipt-to";
227 XSpamStatus, "X-Spam-Status", "x-spam-status";
228 XSpamScore, "X-Spam-Score", "x-spam-score";
229 XSpamFlag, "X-Spam-Flag", "x-spam-flag";
230 XSpamResult, "X-Spam-Result", "x-spam-result";
231 XPriority, "X-Priority", "x-priority";
232 XMSMailPriority, "X-MSMail-Priority", "x-msmail-priority";
233 XMailer, "X-Mailer", "x-mailer";
234 UserAgent, "User-Agent", "user-agent";
235 XMimeOLE, "X-MimeOLE", "x-mimeole";
236 XOriginatingIp, "X-Originating-IP", "x-originating-ip";
237 XForwardedTo, "X-Forwarded-To", "x-forwarded-to";
238 XForwardedFor, "X-Forwarded-For", "x-forwarded-for";
239 XAutoResponseSuppress, "X-Auto-Response-Suppress", "x-auto-response-suppress";
240 Precedence, "Precedence", "precedence";
241 ThreadIndex, "Thread-Index", "thread-index";
242 ThreadTopic, "Thread-Topic", "thread-topic";
243 FeedbackId, "Feedback-ID", "feedback-id";
244 XUniversallyUniqueIdentifier, "X-Universally-Unique-Identifier", "x-universally-unique-identifier";
245 XVirusScanned, "X-Virus-Scanned", "x-virus-scanned";
246 XMailFrom, "X-MailFrom", "x-mailfrom";
247 XMailmanVersion, "X-Mailman-Version", "x-mailman-version";
248 MessageIdHash, "Message-ID-Hash", "message-id-hash";
249 XMessageIdHash, "X-Message-ID-Hash", "x-message-id-hash";
250 XMailmanRuleMisses, "X-Mailman-Rule-Misses", "x-mailman-rule-misses";
251 XSpamLevel, "X-Spam-Level", "x-spam-level";
252 XReceived, "X-Received", "x-received";
253 XGmMessageState, "X-Gm-Message-State", "x-gm-message-state";
254 XGoogleDkimSignature, "X-Google-DKIM-Signature", "x-google-dkim-signature";
255 XMailboxLine, "X-Mailbox-Line", "x-mailbox-line";
256 XGmGg, "X-Gm-Gg", "x-gm-gg";
257 XOriginalFrom, "X-Original-From", "x-original-from";
258 XGoogleSmtpSource, "X-Google-Smtp-Source", "x-google-smtp-source";
259 XMsTnefCorrelator, "X-MS-TNEF-Correlator", "x-ms-tnef-correlator";
260 XMsHasAttach, "X-MS-Has-Attach", "x-ms-has-attach";
261 XMeProxy, "X-ME-Proxy", "x-me-proxy";
262 XMeProxyCause, "X-ME-Proxy-Cause", "x-me-proxy-cause";
263 XMeSender, "X-ME-Sender", "x-me-sender";
264 XGmFeatures, "X-Gm-Features", "x-gm-features";
265 XMsExchangeTransportCrossTenantHeadersStamped, "X-MS-Exchange-Transport-CrossTenantHeadersStamped", "x-ms-exchange-transport-crosstenantheadersstamped";
266 XMsPublicTrafficType, "X-MS-PublicTrafficType", "x-ms-publictraffictype";
267 XOriginatorOrg, "X-OriginatorOrg", "x-originatororg";
268 XMsExchangeCrossTenantNetworkMessageId, "X-MS-Exchange-CrossTenant-Network-Message-Id", "x-ms-exchange-crosstenant-network-message-id";
269 XMsExchangeCrossTenantAuthSource, "X-MS-Exchange-CrossTenant-AuthSource", "x-ms-exchange-crosstenant-authsource";
270 XMsExchangeCrossTenantAuthAs, "X-MS-Exchange-CrossTenant-AuthAs", "x-ms-exchange-crosstenant-authas";
271 XMsExchangeCrossTenantOriginalArrivalTime, "X-MS-Exchange-CrossTenant-OriginalArrivalTime", "x-ms-exchange-crosstenant-originalarrivaltime";
272 XMicrosoftAntispam, "X-Microsoft-Antispam", "x-microsoft-antispam";
273 XMsExchangeCrossTenantId, "X-MS-Exchange-CrossTenant-Id", "x-ms-exchange-crosstenant-id";
274 XMsTrafficTypeDiagnostic, "X-MS-TrafficTypeDiagnostic", "x-ms-traffictypediagnostic";
275 XMicrosoftAntispamMessageInfo, "X-Microsoft-Antispam-Message-Info", "x-microsoft-antispam-message-info";
276 XMsOffice365FilteringCorrelationId, "X-MS-Office365-Filtering-Correlation-Id", "x-ms-office365-filtering-correlation-id";
277 XMsExchangeCrossTenantFromEntityHeader, "X-MS-Exchange-CrossTenant-FromEntityHeader", "x-ms-exchange-crosstenant-fromentityheader";
278 XMsExchangeAntiSpamMessageDataChunkCount, "X-MS-Exchange-AntiSpam-MessageData-ChunkCount", "x-ms-exchange-antispam-messagedata-chunkcount";
279 XMsExchangeAntiSpamMessageData0, "X-MS-Exchange-AntiSpam-MessageData-0", "x-ms-exchange-antispam-messagedata-0";
280 XMsExchangeSenderADCheck, "X-MS-Exchange-SenderADCheck", "x-ms-exchange-senderadcheck";
281 XForefrontAntispamReport, "X-Forefront-Antispam-Report", "x-forefront-antispam-report";
282 XMsExchangeAntiSpamRelay, "X-MS-Exchange-AntiSpam-Relay", "x-ms-exchange-antispam-relay";
283 XMsExchangeCrossTenantMailboxType, "X-MS-Exchange-CrossTenant-MailboxType", "x-ms-exchange-crosstenant-mailboxtype";
284 XMsExchangeCrossTenantUserPrincipalName, "X-MS-Exchange-CrossTenant-UserPrincipalName", "x-ms-exchange-crosstenant-userprincipalname";
285 XGitHubReason, "X-GitHub-Reason", "x-github-reason";
286 XGitHubRecipientAddress, "X-GitHub-Recipient-Address", "x-github-recipient-address";
287 XGitHubRecipient, "X-GitHub-Recipient", "x-github-recipient";
288 Destinations, "Destinations", "destinations";
289 XGitHubSender, "X-GitHub-Sender", "x-github-sender";
290 XGitHubNotifyPlatform, "X-GitHub-Notify-Platform", "x-github-notify-platform";
291 XThreadId, "X-ThreadId", "x-threadid";
292 XSesOutgoing, "X-SES-Outgoing", "x-ses-outgoing";
293 XProofpointVirusVersion, "X-Proofpoint-Virus-Version", "x-proofpoint-virus-version";
294 XGitHubAssignees, "X-GitHub-Assignees", "x-github-assignees";
295 XGitHubLabels, "X-GitHub-Labels", "x-github-labels";
296 BimiSelector, "BIMI-Selector", "bimi-selector";
297 XStripeEid, "X-Stripe-EID", "x-stripe-eid";
298 XAttachmentId, "X-Attachment-Id", "x-attachment-id";
299 XTestIDTracker, "X-Test-IDTracker", "x-test-idtracker";
300 XIetfIDTracker, "X-IETF-IDTracker", "x-ietf-idtracker";
301 XPmMessageId, "X-Pm-Message-ID", "x-pm-message-id";
302 XAntiAbuse, "X-AntiAbuse", "x-antiabuse";
303 Autocrypt, "Autocrypt", "autocrypt";
304 XComplaintsTo, "X-Complaints-To", "x-complaints-to";
305 XProofpointOrigGuid, "X-Proofpoint-ORIG-GUID", "x-proofpoint-orig-guid";
306 XProofpointGuid, "X-Proofpoint-GUID", "x-proofpoint-guid";
307 XGitHubIssueState, "X-GitHub-IssueState", "x-github-issuestate";
308 XMsReactions, "X-MS-Reactions", "x-ms-reactions";
309 XPmMtaPool, "X-Pm-MTA-Pool", "x-pm-mta-pool";
310 XPmRcpt, "X-Pm-RCPT", "x-pm-rcpt";
311 XProofpointSpamDetailsEnc, "X-Proofpoint-Spam-Details-Enc", "x-proofpoint-spam-details-enc";
312 XPmMessageOptions, "X-Pm-Message-Options", "x-pm-message-options";
313 XSenderId, "X-Sender-ID", "x-sender-id";
314 XGitHubPullRequestStatus, "X-GitHub-PullRequestStatus", "x-github-pullrequeststatus";
315 XAuthorityAnalysis, "X-Authority-Analysis", "x-authority-analysis";
316 XForwardedEncrypted, "X-Forwarded-Encrypted", "x-forwarded-encrypted";
317 XMsExchangeAntiSpamExternalHopMessageDataChunkCount, "X-MS-Exchange-AntiSpam-ExternalHop-MessageData-ChunkCount", "x-ms-exchange-antispam-externalhop-messagedata-chunkcount";
318 XMsExchangeAntiSpamExternalHopMessageData0, "X-MS-Exchange-AntiSpam-ExternalHop-MessageData-0", "x-ms-exchange-antispam-externalhop-messagedata-0";
319 XMsExchangeCrossTenantRmsPersistedConsumerOrg, "X-MS-Exchange-CrossTenant-RMS-PersistedConsumerOrg", "x-ms-exchange-crosstenant-rms-persistedconsumerorg";
320 XExchangeRoutingPolicyChecked, "X-Exchange-RoutingPolicyChecked", "x-exchange-routingpolicychecked";
321 XMsExchangeCrossTenantOriginalAttributedTenantConnectingIp, "X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp", "x-ms-exchange-crosstenant-originalattributedtenantconnectingip";
322 XMsExchangeMessageSentRepresentingType, "X-MS-Exchange-MessageSentRepresentingType", "x-ms-exchange-messagesentrepresentingtype";
323 XMsExchangeTransportCrossTenantHeadersStripped, "X-MS-Exchange-Transport-CrossTenantHeadersStripped", "x-ms-exchange-transport-crosstenantheadersstripped";
324 XMsOffice365FilteringCorrelationIdPrvs, "X-MS-Office365-Filtering-Correlation-Id-Prvs", "x-ms-office365-filtering-correlation-id-prvs";
325 XMicrosoftAntispamMessageInfoOriginal, "X-Microsoft-Antispam-Message-Info-Original", "x-microsoft-antispam-message-info-original";
326 XMsExchangeAuthenticationResults, "X-MS-Exchange-Authentication-Results", "x-ms-exchange-authentication-results";
327 XForefrontAntispamReportUntrusted, "X-Forefront-Antispam-Report-Untrusted", "x-forefront-antispam-report-untrusted";
328 XMsExchangeAtpMessageProperties, "X-MS-Exchange-AtpMessageProperties", "x-ms-exchange-atpmessageproperties";
329 XMicrosoftAntispamUntrusted, "X-Microsoft-Antispam-Untrusted", "x-microsoft-antispam-untrusted";
330 XMsExchangeAntiSpamMessageData1, "X-MS-Exchange-AntiSpam-MessageData-1", "x-ms-exchange-antispam-messagedata-1";
331 XMissiveId, "X-Missive-Id", "x-missive-id";
332 XIadbIpReverse, "X-IADB-IP-Reverse", "x-iadb-ip-reverse";
333 XReportAbuse, "X-Report-Abuse", "x-report-abuse";
334 DkimFilter, "DKIM-Filter", "dkim-filter";
335 XMeReceived, "X-ME-Received", "x-me-received";
336 XProofpointSpamDetails, "X-Proofpoint-Spam-Details", "x-proofpoint-spam-details";
337}
338
339const fn longest(names: &[&str]) -> usize {
340 let mut index = 0;
341 let mut max = 0;
342 while index < names.len() {
343 if names[index].len() > max {
344 max = names[index].len();
345 }
346 index += 1;
347 }
348 max
349}
350
351pub(crate) const MAX_NAME_LEN: usize = longest(&LOWERCASE);
352
353pub(crate) fn known(id: u16) -> Option<&'static HeaderName<'static>> {
354 VARIANTS.get(id as usize)
355}
356
357pub(crate) fn canonical(id: u16) -> &'static str {
358 CANONICAL.get(id as usize).copied().unwrap_or_default()
359}
360
361pub(crate) fn lookup(name: &[u8]) -> Option<u16> {
362 let mut buf = [0u8; MAX_NAME_LEN];
363 let lowercase = buf.get_mut(..name.len())?;
364 lowercase
365 .iter_mut()
366 .zip(name)
367 .for_each(|(out, byte)| *out = byte.to_ascii_lowercase());
368 lookup_lowercase(lowercase)
369}
370
371pub(crate) fn trim_blank_end(name: &[u8]) -> &[u8] {
372 let len = name
373 .iter()
374 .rposition(|&byte| !matches!(byte, b' ' | b'\t'))
375 .map_or(0, |last| last + 1);
376 name.get(..len).unwrap_or_default()
377}
378
379#[inline(never)]
380fn other_id(name: &str) -> u16 {
381 lookup(name.as_bytes()).unwrap_or(OTHER_ID)
382}
383
384fn is_ftext(byte: u8) -> bool {
385 matches!(byte, b'!'..=b'9' | b';'..=b'~')
386}
387
388impl<'x> HeaderName<'x> {
389 pub fn parse(name: impl Into<Cow<'x, str>>) -> Option<HeaderName<'x>> {
393 let name = name.into();
394 (!name.is_empty() && name.bytes().all(is_ftext)).then(|| HeaderName::resolve(name))
395 }
396
397 fn resolve(name: Cow<'x, str>) -> HeaderName<'x> {
398 match lookup(name.as_bytes()).and_then(known) {
399 Some(known) => known.clone(),
400 None => HeaderName::Other(name),
401 }
402 }
403
404 #[inline]
424 pub fn key(&self) -> HeaderKey<'_> {
425 HeaderKey {
426 id: self.resolved_id(),
427 name: self.as_str(),
428 }
429 }
430
431 #[inline]
432 pub(crate) fn resolved_id(&self) -> u16 {
433 match self {
434 HeaderName::Other(name) => other_id(name),
435 known => known.id(),
436 }
437 }
438
439 pub fn into_owned(self) -> HeaderName<'static> {
441 match self {
442 HeaderName::Other(name) => HeaderName::Other(name.into_owned().into()),
443 known => known
444 .known_static()
445 .cloned()
446 .unwrap_or(HeaderName::Other(Cow::Borrowed(""))),
447 }
448 }
449
450 fn known_static(&self) -> Option<&'static HeaderName<'static>> {
451 match self {
452 HeaderName::Other(_) => None,
453 known => VARIANTS.get(known.id() as usize),
454 }
455 }
456
457 pub fn as_static_str(&self) -> &'static str {
459 match self {
460 HeaderName::Other(_) => "",
461 known => CANONICAL
462 .get(known.id() as usize)
463 .copied()
464 .unwrap_or_default(),
465 }
466 }
467
468 pub fn as_str(&self) -> &str {
471 match self {
472 HeaderName::Other(name) => name.as_ref(),
473 known => known.as_static_str(),
474 }
475 }
476
477 pub fn is_other(&self) -> bool {
479 matches!(self, HeaderName::Other(_))
480 }
481
482 pub fn is_mime_header(&self) -> bool {
484 matches!(
485 self,
486 HeaderName::ContentDescription
487 | HeaderName::ContentId
488 | HeaderName::ContentLanguage
489 | HeaderName::ContentLocation
490 | HeaderName::ContentTransferEncoding
491 | HeaderName::ContentType
492 | HeaderName::ContentDisposition
493 )
494 }
495
496 pub fn is_content_header(&self) -> bool {
499 match self {
500 HeaderName::ContentDescription
501 | HeaderName::ContentId
502 | HeaderName::ContentLanguage
503 | HeaderName::ContentLocation
504 | HeaderName::ContentTransferEncoding
505 | HeaderName::ContentType
506 | HeaderName::ContentDisposition
507 | HeaderName::ContentAlternative
508 | HeaderName::ContentDuration
509 | HeaderName::ContentFeatures
510 | HeaderName::ContentMd5
511 | HeaderName::ContentTranslationType => true,
512 HeaderName::Other(name) => name
513 .as_bytes()
514 .get(..CONTENT_PREFIX.len())
515 .is_some_and(|prefix| prefix.eq_ignore_ascii_case(CONTENT_PREFIX)),
516 _ => false,
517 }
518 }
519
520 pub fn is_structured(&self) -> bool {
522 matches!(
523 self,
524 HeaderName::Subject
525 | HeaderName::Comments
526 | HeaderName::ContentDescription
527 | HeaderName::ContentLocation
528 | HeaderName::ContentTransferEncoding
529 | HeaderName::From
530 | HeaderName::To
531 | HeaderName::Cc
532 | HeaderName::Bcc
533 | HeaderName::ReplyTo
534 | HeaderName::Sender
535 | HeaderName::ResentTo
536 | HeaderName::ResentFrom
537 | HeaderName::ResentBcc
538 | HeaderName::ResentCc
539 | HeaderName::ResentSender
540 | HeaderName::ListArchive
541 | HeaderName::ListHelp
542 | HeaderName::ListId
543 | HeaderName::ListOwner
544 | HeaderName::ListPost
545 | HeaderName::ListSubscribe
546 | HeaderName::ListUnsubscribe
547 | HeaderName::Date
548 | HeaderName::ResentDate
549 | HeaderName::MessageId
550 | HeaderName::References
551 | HeaderName::InReplyTo
552 | HeaderName::ReturnPath
553 | HeaderName::ContentId
554 | HeaderName::ResentMessageId
555 | HeaderName::Keywords
556 | HeaderName::ContentLanguage
557 | HeaderName::Received
558 | HeaderName::ContentType
559 | HeaderName::ContentDisposition
560 )
561 }
562}
563
564impl PartialEq for HeaderName<'_> {
565 fn eq(&self, other: &Self) -> bool {
566 match (self, other) {
567 (HeaderName::Other(a), HeaderName::Other(b)) => a.eq_ignore_ascii_case(b),
568 (HeaderName::Other(name), known) | (known, HeaderName::Other(name)) => {
569 name.eq_ignore_ascii_case(known.as_static_str())
570 }
571 _ => self.id() == other.id(),
572 }
573 }
574}
575
576impl Eq for HeaderName<'_> {}
577
578impl Hash for HeaderName<'_> {
579 fn hash<H: Hasher>(&self, state: &mut H) {
580 match (self, self.resolved_id()) {
581 (HeaderName::Other(name), OTHER_ID) => name
582 .bytes()
583 .for_each(|byte| byte.to_ascii_lowercase().hash(state)),
584 (_, id) => id.hash(state),
585 }
586 }
587}
588
589impl PartialOrd for HeaderName<'_> {
590 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
591 Some(self.cmp(other))
592 }
593}
594
595impl Ord for HeaderName<'_> {
596 fn cmp(&self, other: &Self) -> Ordering {
597 match (self.resolved_id(), other.resolved_id()) {
598 (OTHER_ID, OTHER_ID) => self
599 .as_str()
600 .bytes()
601 .map(|byte| byte.to_ascii_lowercase())
602 .cmp(other.as_str().bytes().map(|byte| byte.to_ascii_lowercase())),
603 (a, b) => a.cmp(&b),
604 }
605 }
606}
607
608impl fmt::Display for HeaderName<'_> {
609 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
610 f.write_str(self.as_str())
611 }
612}
613
614impl<'x> From<&'x str> for HeaderName<'x> {
615 fn from(value: &'x str) -> Self {
616 HeaderName::resolve(Cow::Borrowed(value))
617 }
618}
619
620impl<'x> From<Cow<'x, str>> for HeaderName<'x> {
621 fn from(value: Cow<'x, str>) -> Self {
622 HeaderName::resolve(value)
623 }
624}
625
626impl From<String> for HeaderName<'_> {
627 fn from(value: String) -> Self {
628 HeaderName::resolve(Cow::Owned(value))
629 }
630}
631
632impl<'x> From<&HeaderName<'x>> for HeaderName<'x> {
633 fn from(value: &HeaderName<'x>) -> Self {
634 value.clone()
635 }
636}
637
638impl<'x> From<HeaderName<'x>> for Cow<'x, str> {
639 fn from(name: HeaderName<'x>) -> Self {
640 match name {
641 HeaderName::Other(name) => name,
642 known => Cow::Borrowed(known.as_static_str()),
643 }
644 }
645}
646
647impl From<HeaderName<'_>> for String {
648 fn from(name: HeaderName<'_>) -> Self {
649 name.as_str().to_string()
650 }
651}
652
653#[cfg(feature = "serde")]
654impl serde::Serialize for HeaderName<'_> {
655 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
656 serializer.serialize_str(self.as_str())
657 }
658}
659
660#[cfg(feature = "serde")]
661impl<'de> serde::Deserialize<'de> for HeaderName<'static> {
662 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
663 String::deserialize(deserializer).map(HeaderName::from)
664 }
665}
666
667#[cfg(test)]
668mod tests {
669 use super::*;
670
671 const BASELINE_NAMES: &str = include_str!("../resources/header_names.txt");
672
673 #[test]
674 fn baseline_names_round_trip() {
675 for name in BASELINE_NAMES.lines() {
676 let parsed = HeaderName::parse(name).expect("valid name");
677 assert!(!parsed.is_other(), "{name} is not known");
678 assert_eq!(parsed.as_str(), name);
679 assert_eq!(HeaderName::parse(parsed.as_str()), Some(parsed.clone()));
680 assert_eq!(
681 HeaderName::parse(name.to_ascii_uppercase()),
682 Some(parsed.clone())
683 );
684 }
685 }
686
687 #[test]
688 fn vendor_names_follow_the_baseline_names() {
689 assert_eq!(FIRST_VENDOR as usize, BASELINE_NAMES.lines().count());
690 for (id, name) in BASELINE_NAMES.lines().enumerate() {
691 assert_eq!(canonical(id as u16), name);
692 }
693 }
694
695 #[test]
696 fn every_known_name_round_trips() {
697 for (canonical, lowercase) in CANONICAL.iter().zip(LOWERCASE.iter()) {
698 assert_eq!(&canonical.to_ascii_lowercase(), lowercase);
699 let parsed = HeaderName::parse(*canonical).expect("valid");
700 assert_eq!(parsed.as_str(), *canonical);
701 assert_eq!(lookup(lowercase.as_bytes()), Some(parsed.id()));
702 }
703 assert_eq!(KNOWN_NAMES, 252);
704 assert_eq!(MAX_NAME_LEN, 62);
705 }
706
707 #[test]
708 fn content_headers_match_the_content_prefix() {
709 for (variant, lowercase) in VARIANTS.iter().zip(LOWERCASE.iter()) {
711 assert_eq!(
712 variant.is_content_header(),
713 lowercase.starts_with("content-"),
714 "{lowercase}"
715 );
716 }
717 assert!(HeaderName::Other("CONTENT-Base".into()).is_content_header());
718 assert!(!HeaderName::Other("Contents".into()).is_content_header());
719 assert!(!HeaderName::MimeVersion.is_content_header());
720 }
721
722 #[test]
723 fn other_names() {
724 let custom = HeaderName::parse("X-Custom-Field").expect("valid");
725 assert_eq!(custom, HeaderName::Other("x-custom-field".into()));
726 assert_eq!(custom.as_str(), "X-Custom-Field");
727 assert!(HeaderName::parse("").is_none());
728 assert!(HeaderName::parse("mal formed").is_none());
729 assert_eq!(HeaderName::parse("X-Mailer"), Some(HeaderName::XMailer));
730 assert_eq!(
731 HeaderName::parse("x-face").map(|n| n.is_other()),
732 Some(true)
733 );
734 assert_eq!(
735 HeaderName::parse("Original-Encoded-Information-Types-Extra").map(|n| n.is_other()),
736 Some(true)
737 );
738 assert_eq!(lookup(b"SUBJECT"), Some(HeaderName::Subject.id()));
739 for name in [
740 &b" subject"[..],
741 b"Sub ject",
742 b"MIME-version ",
743 b"From\r",
744 b"F\x0crom",
745 b":From",
746 b"",
747 ] {
748 assert_eq!(lookup(name), None, "{name:?}");
749 }
750 }
751
752 #[test]
753 fn every_field_name_character_is_accepted() {
754 for name in ["X.Spam", "A@[]{}~", "X!#$%&'*+/=?^`|", "X;Y"] {
755 let parsed = HeaderName::parse(name).expect("valid field name");
756 assert_eq!(parsed, HeaderName::Other(name.into()));
757 assert_eq!(parsed.as_str(), name);
758 }
759 for name in ["", "X:Y", "X Y", "caf\u{e9}", "X\u{7f}", "X\tY"] {
760 assert_eq!(HeaderName::parse(name), None, "{name:?}");
761 }
762 for name in ["X.Spam", "mal formed", ":From", "caf\u{e9}", ""] {
763 assert_eq!(HeaderName::from(name).as_str(), name);
764 assert_eq!(HeaderName::from(name.to_string()).as_str(), name);
765 assert_eq!(HeaderName::from(Cow::Borrowed(name)).as_str(), name);
766 }
767 assert_eq!(HeaderName::from("x-mailer"), HeaderName::XMailer);
768 assert!(HeaderName::from("Sub ject").is_other());
769 }
770
771 #[test]
772 fn other_spelling_a_known_name_is_that_name() {
773 use std::collections::{BTreeSet, HashSet};
774
775 let pairs = [
776 (HeaderName::Other("Subject".into()), HeaderName::Subject),
777 (HeaderName::Other("x-MAILER".into()), HeaderName::XMailer),
778 (
779 HeaderName::Other("content-type".into()),
780 HeaderName::ContentType,
781 ),
782 ];
783 for (other, known) in &pairs {
784 assert_eq!(other, known);
785 assert_eq!(known, other);
786 assert_eq!(other.cmp(known), Ordering::Equal);
787 assert_eq!(other.resolved_id(), known.id());
788 let hashed: HashSet<HeaderName<'_>> = [other.clone()].into_iter().collect();
789 assert!(hashed.contains(known));
790 let ordered: BTreeSet<HeaderName<'_>> = [known.clone()].into_iter().collect();
791 assert!(ordered.contains(other));
792 }
793 let custom = HeaderName::Other("X-Custom".into());
794 assert_ne!(custom, HeaderName::Subject);
795 assert_ne!(HeaderName::Other("Sub ject".into()), HeaderName::Subject);
796 assert_eq!(custom.cmp(&HeaderName::Subject), Ordering::Greater);
797 assert_eq!(custom.resolved_id(), OTHER_ID);
798 let mut names = [
799 HeaderName::Other("x-b".into()),
800 HeaderName::Other("From".into()),
801 HeaderName::Subject,
802 HeaderName::Other("X-A".into()),
803 ];
804 names.sort();
805 assert_eq!(
806 names.iter().map(HeaderName::as_str).collect::<Vec<_>>(),
807 ["Subject", "From", "X-A", "x-b"]
808 );
809 }
810}
811
812#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
815pub struct HeaderKey<'x> {
816 id: u16,
817 name: &'x str,
818}
819
820impl<'x> HeaderKey<'x> {
821 pub fn as_str(&self) -> &'x str {
823 self.name
824 }
825
826 pub(crate) fn id(&self) -> u16 {
827 self.id
828 }
829}