Skip to main content

mail_parser/view/
headers.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use super::{AddressList, ContentType, Received, Resolver};
8use crate::{
9    DateTime, HeaderForm, HeaderName, ParsedValue,
10    header_name::{self, HeaderId, HeaderKey},
11    store::{HeaderEntry, MessageData, NONE, OTHER_NAME, Str, Value},
12    thread_name,
13};
14use std::{borrow::Cow, fmt, iter::FusedIterator, slice};
15
16/// The header fields of one part, in the order they were written.
17///
18/// Accessors that return one field ([`Headers::get`], [`Headers::value`],
19/// [`Headers::subject`], ...) return the last occurrence of the name, as
20/// 0.11 did. Names compare ASCII case-insensitively.
21///
22/// ```
23/// use mail_parser::{HeaderName, MessageParser};
24///
25/// let raw = b"Subject: first\r\nX-Tag: a\r\nSubject: second\r\n\r\nbody";
26/// let message = MessageParser::new().parse(raw).expect("message");
27/// let headers = message.headers();
28/// assert_eq!(headers.len(), 3);
29/// assert_eq!(headers.subject(), Some("second"));
30/// assert_eq!(headers.all(HeaderName::Subject).count(), 2);
31/// assert_eq!(headers.get("x-tag").map(|header| header.raw_value()), Some(&b" a\r\n"[..]));
32/// ```
33#[derive(Clone, Copy)]
34pub struct Headers<'m> {
35    resolver: Resolver<'m>,
36    data: &'m MessageData,
37    entries: &'m [HeaderEntry],
38}
39
40/// One header field: its name, its parsed value, and where it is in the
41/// source buffer of its message (see [`crate::MessageRef::source`]).
42#[derive(Clone, Copy)]
43pub struct Header<'m> {
44    resolver: Resolver<'m>,
45    data: &'m MessageData,
46    entry: &'m HeaderEntry,
47}
48
49/// A parsed header value. The variant follows the [`HeaderForm`] the parser
50/// used for the name (see [`crate::MessageParser::header`]). Values compare
51/// by content.
52#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
53pub enum HeaderValue<'m> {
54    /// No parsable value: a value that does not parse in its form (an empty
55    /// address list, date or Content-Type included), or a field parsed with
56    /// [`HeaderForm::Ignore`].
57    #[default]
58    Empty,
59    /// The trimmed field ([`HeaderForm::Raw`]) or its decoded text
60    /// ([`HeaderForm::Text`]); an empty string for a field that is present
61    /// but blank.
62    Text(&'m str),
63    /// Message identifiers ([`HeaderForm::MessageIds`]) or phrases
64    /// ([`HeaderForm::CommaList`]); always a list, even with one item.
65    TextList(TextList<'m>),
66    /// Mailboxes and groups ([`HeaderForm::Addresses`]).
67    Address(AddressList<'m>),
68    /// A date ([`HeaderForm::Date`]).
69    DateTime(DateTime),
70    /// A Content-Type or Content-Disposition value
71    /// ([`HeaderForm::ContentType`]).
72    ContentType(ContentType<'m>),
73    /// A Received trace field ([`HeaderForm::Received`]).
74    Received(Received<'m>),
75}
76
77/// The headers with one name, returned by [`Headers::all`].
78#[derive(Clone)]
79pub struct NamedHeaders<'m, 'n> {
80    headers: Headers<'m>,
81    rest: &'m [HeaderEntry],
82    name: HeaderName<'n>,
83    id: u16,
84}
85
86impl<'m> Iterator for NamedHeaders<'m, '_> {
87    type Item = Header<'m>;
88
89    #[inline]
90    fn next(&mut self) -> Option<Header<'m>> {
91        while let Some((entry, rest)) = self.rest.split_first() {
92            self.rest = rest;
93            if self.headers.matches(entry, self.name.as_str(), self.id) {
94                return Some(self.headers.header(entry));
95            }
96        }
97        None
98    }
99}
100
101/// Every header of a part in document order, returned by [`Headers::iter`]
102/// and by iterating a [`Headers`] value.
103#[derive(Clone)]
104pub struct HeaderIter<'m> {
105    headers: Headers<'m>,
106    entries: slice::Iter<'m, HeaderEntry>,
107}
108
109impl<'m> Iterator for HeaderIter<'m> {
110    type Item = Header<'m>;
111
112    fn next(&mut self) -> Option<Header<'m>> {
113        self.entries.next().map(|entry| self.headers.header(entry))
114    }
115
116    fn size_hint(&self) -> (usize, Option<usize>) {
117        self.entries.size_hint()
118    }
119}
120
121impl<'m> DoubleEndedIterator for HeaderIter<'m> {
122    fn next_back(&mut self) -> Option<Header<'m>> {
123        self.entries
124            .next_back()
125            .map(|entry| self.headers.header(entry))
126    }
127}
128
129impl ExactSizeIterator for HeaderIter<'_> {}
130
131impl FusedIterator for HeaderIter<'_> {}
132
133impl<'m> IntoIterator for Headers<'m> {
134    type Item = Header<'m>;
135    type IntoIter = HeaderIter<'m>;
136
137    fn into_iter(self) -> HeaderIter<'m> {
138        self.iter()
139    }
140}
141
142impl<'m> IntoIterator for &Headers<'m> {
143    type Item = Header<'m>;
144    type IntoIter = HeaderIter<'m>;
145
146    fn into_iter(self) -> HeaderIter<'m> {
147        self.iter()
148    }
149}
150
151/// A list of text items: message identifiers (without angle brackets),
152/// keywords, languages. Lists compare by content.
153#[derive(Clone, Copy)]
154pub struct TextList<'m> {
155    resolver: Resolver<'m>,
156    items: &'m [Str],
157}
158
159impl<'m> Headers<'m> {
160    pub(crate) fn new(
161        resolver: Resolver<'m>,
162        data: &'m MessageData,
163        entries: &'m [HeaderEntry],
164    ) -> Self {
165        Headers {
166            resolver,
167            data,
168            entries,
169        }
170    }
171
172    #[inline]
173    fn header(&self, entry: &'m HeaderEntry) -> Header<'m> {
174        Header {
175            resolver: self.resolver,
176            data: self.data,
177            entry,
178        }
179    }
180
181    /// Number of header fields.
182    pub fn len(&self) -> usize {
183        self.entries.len()
184    }
185
186    /// Whether the part has no header field.
187    pub fn is_empty(&self) -> bool {
188        self.entries.is_empty()
189    }
190
191    /// Every header in document order. `for header in headers` does the
192    /// same.
193    pub fn iter(&self) -> HeaderIter<'m> {
194        HeaderIter {
195            headers: *self,
196            entries: self.entries.iter(),
197        }
198    }
199
200    #[inline]
201    fn matches(&self, entry: &HeaderEntry, name: &str, id: u16) -> bool {
202        if id != OTHER_NAME {
203            entry.name == id
204        } else {
205            entry.name == OTHER_NAME
206                && self
207                    .resolver
208                    .raw_header_name(self.data, entry)
209                    .eq_ignore_ascii_case(name)
210        }
211    }
212
213    /// The last header with this name. A [`HeaderName::Other`] spelled
214    /// like a known name finds that name.
215    pub fn get<'n>(&self, name: impl Into<HeaderName<'n>>) -> Option<Header<'m>> {
216        let name = name.into();
217        self.find_last(name.as_str(), name.resolved_id())
218    }
219
220    /// The last header with the name of this key; see [`HeaderName::key`].
221    #[inline]
222    pub fn get_key(&self, key: HeaderKey<'_>) -> Option<Header<'m>> {
223        self.find_last(key.as_str(), key.id())
224    }
225
226    #[inline]
227    fn find_last(&self, name: &str, id: u16) -> Option<Header<'m>> {
228        match id {
229            OTHER_NAME => self
230                .entries
231                .iter()
232                .rev()
233                .find(|entry| self.matches(entry, name, OTHER_NAME))
234                .map(|entry| self.header(entry)),
235            id => self.last_known(id),
236        }
237    }
238
239    #[inline]
240    fn last_known(&self, id: u16) -> Option<Header<'m>> {
241        self.entries
242            .iter()
243            .rev()
244            .find(|entry| entry.name == id)
245            .map(|entry| self.header(entry))
246    }
247
248    #[inline]
249    fn known_value(&self, id: HeaderId) -> Option<HeaderValue<'m>> {
250        self.last_known(id as u16).map(|header| header.value())
251    }
252
253    /// Every header with this name, in document order. A
254    /// [`HeaderName::Other`] spelled like a known name finds that name.
255    #[inline]
256    pub fn all<'n>(&self, name: impl Into<HeaderName<'n>>) -> NamedHeaders<'m, 'n> {
257        let name = name.into();
258        NamedHeaders {
259            headers: *self,
260            rest: self.entries,
261            id: name.resolved_id(),
262            name,
263        }
264    }
265
266    /// Every header with the name of this key, in document order; see
267    /// [`HeaderName::key`].
268    #[inline]
269    pub fn all_key<'k>(&self, key: HeaderKey<'k>) -> NamedHeaders<'m, 'k> {
270        NamedHeaders {
271            headers: *self,
272            rest: self.entries,
273            id: key.id(),
274            name: HeaderName::Other(Cow::Borrowed(key.as_str())),
275        }
276    }
277
278    /// Whether a header with this name is present.
279    pub fn contains<'n>(&self, name: impl Into<HeaderName<'n>>) -> bool {
280        self.get(name).is_some()
281    }
282
283    /// Whether at least one header has a known name: a cheap test that the
284    /// input looks like a message rather than arbitrary text.
285    pub fn has_known(&self) -> bool {
286        self.entries.iter().any(|entry| entry.name != OTHER_NAME)
287    }
288
289    /// The value of the last header with this name.
290    pub fn value<'n>(&self, name: impl Into<HeaderName<'n>>) -> Option<HeaderValue<'m>> {
291        self.get(name).map(|header| header.value())
292    }
293
294    fn text(&self, id: HeaderId) -> Option<&'m str> {
295        self.known_value(id)?.as_text()
296    }
297
298    fn address(&self, id: HeaderId) -> Option<AddressList<'m>> {
299        self.known_value(id)?.as_address()
300    }
301
302    fn text_list(&self, id: HeaderId) -> Option<TextList<'m>> {
303        self.known_value(id)?.as_text_list()
304    }
305
306    fn all_addresses(
307        &self,
308        name: HeaderName<'static>,
309    ) -> impl Iterator<Item = AddressList<'m>> + use<'m> {
310        self.all(name)
311            .filter_map(|header| header.value().as_address())
312    }
313
314    /// The Subject field, RFC 2047 encoded words decoded.
315    pub fn subject(&self) -> Option<&'m str> {
316        self.text(HeaderId::Subject)
317    }
318
319    /// The Comments field, decoded.
320    pub fn comments(&self) -> Option<&'m str> {
321        self.text(HeaderId::Comments)
322    }
323
324    /// The MIME-Version field, as written.
325    pub fn mime_version(&self) -> Option<&'m str> {
326        self.text(HeaderId::MimeVersion)
327    }
328
329    /// The subject with reply and forward markers removed; see
330    /// [`crate::thread_name`].
331    pub fn thread_name(&self) -> Option<&'m str> {
332        self.subject().map(thread_name)
333    }
334
335    /// The From field.
336    pub fn from(&self) -> Option<AddressList<'m>> {
337        self.address(HeaderId::From)
338    }
339
340    /// The To field.
341    pub fn to(&self) -> Option<AddressList<'m>> {
342        self.address(HeaderId::To)
343    }
344
345    /// The Cc field.
346    pub fn cc(&self) -> Option<AddressList<'m>> {
347        self.address(HeaderId::Cc)
348    }
349
350    /// The Bcc field.
351    pub fn bcc(&self) -> Option<AddressList<'m>> {
352        self.address(HeaderId::Bcc)
353    }
354
355    /// The Reply-To field.
356    pub fn reply_to(&self) -> Option<AddressList<'m>> {
357        self.address(HeaderId::ReplyTo)
358    }
359
360    /// The Sender field.
361    pub fn sender(&self) -> Option<AddressList<'m>> {
362        self.address(HeaderId::Sender)
363    }
364
365    /// Every From field, in document order.
366    pub fn all_from(&self) -> impl Iterator<Item = AddressList<'m>> + use<'m> {
367        self.all_addresses(HeaderName::From)
368    }
369
370    /// Every To field, in document order.
371    pub fn all_to(&self) -> impl Iterator<Item = AddressList<'m>> + use<'m> {
372        self.all_addresses(HeaderName::To)
373    }
374
375    /// Every Cc field, in document order.
376    pub fn all_cc(&self) -> impl Iterator<Item = AddressList<'m>> + use<'m> {
377        self.all_addresses(HeaderName::Cc)
378    }
379
380    /// Every Bcc field, in document order.
381    pub fn all_bcc(&self) -> impl Iterator<Item = AddressList<'m>> + use<'m> {
382        self.all_addresses(HeaderName::Bcc)
383    }
384
385    /// The Resent-To field.
386    pub fn resent_to(&self) -> Option<AddressList<'m>> {
387        self.address(HeaderId::ResentTo)
388    }
389
390    /// The Resent-From field.
391    pub fn resent_from(&self) -> Option<AddressList<'m>> {
392        self.address(HeaderId::ResentFrom)
393    }
394
395    /// The Resent-Cc field.
396    pub fn resent_cc(&self) -> Option<AddressList<'m>> {
397        self.address(HeaderId::ResentCc)
398    }
399
400    /// The Resent-Bcc field.
401    pub fn resent_bcc(&self) -> Option<AddressList<'m>> {
402        self.address(HeaderId::ResentBcc)
403    }
404
405    /// The Resent-Sender field.
406    pub fn resent_sender(&self) -> Option<AddressList<'m>> {
407        self.address(HeaderId::ResentSender)
408    }
409
410    /// The List-Archive field (RFC 2369); the URLs are in
411    /// [`crate::Mailbox::address`].
412    pub fn list_archive(&self) -> Option<AddressList<'m>> {
413        self.address(HeaderId::ListArchive)
414    }
415
416    /// The List-Help field (RFC 2369); the URLs are in
417    /// [`crate::Mailbox::address`].
418    pub fn list_help(&self) -> Option<AddressList<'m>> {
419        self.address(HeaderId::ListHelp)
420    }
421
422    /// The List-ID field (RFC 2919): the description in
423    /// [`crate::Mailbox::name`], the identifier in [`crate::Mailbox::address`].
424    pub fn list_id(&self) -> Option<AddressList<'m>> {
425        self.address(HeaderId::ListId)
426    }
427
428    /// The List-Owner field (RFC 2369); the URLs are in
429    /// [`crate::Mailbox::address`].
430    pub fn list_owner(&self) -> Option<AddressList<'m>> {
431        self.address(HeaderId::ListOwner)
432    }
433
434    /// The List-Post field (RFC 2369); the URLs are in
435    /// [`crate::Mailbox::address`].
436    pub fn list_post(&self) -> Option<AddressList<'m>> {
437        self.address(HeaderId::ListPost)
438    }
439
440    /// The List-Subscribe field (RFC 2369); the URLs are in
441    /// [`crate::Mailbox::address`].
442    pub fn list_subscribe(&self) -> Option<AddressList<'m>> {
443        self.address(HeaderId::ListSubscribe)
444    }
445
446    /// The List-Unsubscribe field (RFC 2369); the URLs are in
447    /// [`crate::Mailbox::address`].
448    pub fn list_unsubscribe(&self) -> Option<AddressList<'m>> {
449        self.address(HeaderId::ListUnsubscribe)
450    }
451
452    /// The Date field.
453    pub fn date(&self) -> Option<DateTime> {
454        self.known_value(HeaderId::Date)?.as_datetime()
455    }
456
457    /// The Resent-Date field.
458    pub fn resent_date(&self) -> Option<DateTime> {
459        self.known_value(HeaderId::ResentDate)?.as_datetime()
460    }
461
462    /// The last identifier of the Message-ID field, without angle brackets.
463    pub fn message_id(&self) -> Option<&'m str> {
464        self.known_value(HeaderId::MessageId)?.as_text()
465    }
466
467    /// The identifiers of the In-Reply-To field.
468    pub fn in_reply_to(&self) -> Option<TextList<'m>> {
469        self.text_list(HeaderId::InReplyTo)
470    }
471
472    /// The identifiers of the References field.
473    pub fn references(&self) -> Option<TextList<'m>> {
474        self.text_list(HeaderId::References)
475    }
476
477    /// The identifiers of the Resent-Message-ID field.
478    pub fn resent_message_id(&self) -> Option<TextList<'m>> {
479        self.text_list(HeaderId::ResentMessageId)
480    }
481
482    /// The Return-Path field, parsed as message identifiers (the address
483    /// without angle brackets).
484    pub fn return_path(&self) -> Option<TextList<'m>> {
485        self.text_list(HeaderId::ReturnPath)
486    }
487
488    /// The phrases of the Keywords field.
489    pub fn keywords(&self) -> Option<TextList<'m>> {
490        self.text_list(HeaderId::Keywords)
491    }
492
493    /// The last address of Return-Path, or else the first address of From.
494    pub fn return_address(&self) -> Option<&'m str> {
495        match self.known_value(HeaderId::ReturnPath) {
496            Some(value @ (HeaderValue::Text(_) | HeaderValue::TextList(_))) => value.as_text(),
497            _ => self.from()?.first()?.address(),
498        }
499    }
500
501    /// The last Received field (the oldest hop).
502    pub fn received(&self) -> Option<Received<'m>> {
503        self.known_value(HeaderId::Received)?.as_received()
504    }
505
506    /// Every Received field, in document order.
507    pub fn all_received(&self) -> impl Iterator<Item = Received<'m>> + use<'m> {
508        self.all(HeaderName::Received)
509            .filter_map(|header| header.value().as_received())
510    }
511}
512
513impl<'m> Resolver<'m> {
514    fn raw_header_name(self, data: &'m MessageData, entry: &HeaderEntry) -> &'m str {
515        if entry.other_name != NONE {
516            return data
517                .text_items
518                .get(entry.other_name as usize)
519                .map_or("", |text| self.str(*text));
520        }
521        let start = entry.offset_field as usize;
522        let region = self
523            .source()
524            .get(start..(entry.offset_start as usize).saturating_sub(1))
525            .unwrap_or_default();
526        let canonical = header_name::canonical(entry.name);
527        if region == canonical.as_bytes() {
528            return canonical;
529        }
530        let name = header_name::trim_blank_end(region);
531        self.borrow(start..start + name.len()).unwrap_or_default()
532    }
533}
534
535impl<'m> Header<'m> {
536    /// The name: a known variant, or `Other` with the original spelling.
537    #[inline]
538    pub fn name(&self) -> HeaderName<'m> {
539        match header_name::known(self.entry.name) {
540            Some(known) => known.clone(),
541            None => HeaderName::Other(Cow::Borrowed(self.raw_name())),
542        }
543    }
544
545    /// The name as written in the message, original case kept.
546    pub fn raw_name(&self) -> &'m str {
547        self.resolver.raw_header_name(self.data, self.entry)
548    }
549
550    /// The value, parsed with the form the parser used for this name.
551    #[inline]
552    pub fn value(&self) -> HeaderValue<'m> {
553        HeaderValue::new(self.resolver, self.data, self.entry.value)
554    }
555
556    /// The bytes after the colon up to the end of the field, final line
557    /// break included.
558    pub fn raw_value(&self) -> &'m [u8] {
559        self.resolver
560            .source()
561            .get(self.entry.offset_start as usize..self.entry.offset_end as usize)
562            .unwrap_or_default()
563    }
564
565    /// Parses the raw value again with another form, for names the parser
566    /// kept raw or parsed differently.
567    ///
568    /// ```
569    /// use mail_parser::{HeaderForm, MessageParser};
570    ///
571    /// let raw = b"X-Sender: Jane <jane@example.com>\r\n\r\nbody";
572    /// let message = MessageParser::new().parse(raw).expect("message");
573    /// let header = message.headers().get("x-sender").expect("header");
574    /// let parsed = header.parse_as(HeaderForm::Addresses);
575    /// let sender = parsed.value().as_address().and_then(|list| list.first());
576    /// assert_eq!(sender.and_then(|mailbox| mailbox.address()), Some("jane@example.com"));
577    /// ```
578    pub fn parse_as(&self, form: HeaderForm) -> ParsedValue<'m> {
579        ParsedValue::new(
580            form,
581            self.resolver.source(),
582            self.entry.offset_start as usize..self.entry.offset_end as usize,
583        )
584    }
585
586    /// Offset of the first byte of the name in the message's source buffer.
587    pub fn offset_field(&self) -> u32 {
588        self.entry.offset_field
589    }
590
591    /// Offset of the byte after the colon.
592    pub fn offset_start(&self) -> u32 {
593        self.entry.offset_start
594    }
595
596    /// Offset of the byte after the final line break of the field. For the
597    /// last field of a header block cut short by a MIME delimiter, this is
598    /// one line break past the part's body offset.
599    pub fn offset_end(&self) -> u32 {
600        self.entry.offset_end
601    }
602}
603
604impl<'m> HeaderValue<'m> {
605    #[inline]
606    pub(crate) fn new(resolver: Resolver<'m>, data: &'m MessageData, value: Value) -> Self {
607        match value {
608            Value::Empty => HeaderValue::Empty,
609            Value::Text(text) => HeaderValue::Text(resolver.str(text)),
610            Value::TextList(span) => HeaderValue::TextList(TextList {
611                resolver,
612                items: data.text_items.get(span.range()).unwrap_or_default(),
613            }),
614            Value::Address(span) => HeaderValue::Address(AddressList::new(
615                resolver,
616                data.address_items.get(span.range()).unwrap_or_default(),
617            )),
618            Value::DateTime(date) => HeaderValue::DateTime(date),
619            Value::ContentType(index) => data
620                .content_types
621                .get(index as usize)
622                .map_or(HeaderValue::Empty, |entry| {
623                    HeaderValue::ContentType(ContentType::new(resolver, data, entry))
624                }),
625            Value::Received(index) => data
626                .received
627                .get(index as usize)
628                .map_or(HeaderValue::Empty, |entry| {
629                    HeaderValue::Received(Received::new(resolver, entry))
630                }),
631        }
632    }
633
634    /// Whether the value is [`HeaderValue::Empty`].
635    pub fn is_empty(&self) -> bool {
636        matches!(self, HeaderValue::Empty)
637    }
638
639    /// The text, or the last item of a text list (the identifier of a
640    /// Message-ID field, for instance).
641    pub fn as_text(&self) -> Option<&'m str> {
642        match self {
643            HeaderValue::Text(text) => Some(text),
644            HeaderValue::TextList(list) => list.last(),
645            _ => None,
646        }
647    }
648
649    /// The text list.
650    pub fn as_text_list(&self) -> Option<TextList<'m>> {
651        match self {
652            HeaderValue::TextList(list) => Some(*list),
653            _ => None,
654        }
655    }
656
657    /// The address list.
658    pub fn as_address(&self) -> Option<AddressList<'m>> {
659        match self {
660            HeaderValue::Address(list) => Some(*list),
661            _ => None,
662        }
663    }
664
665    /// The date.
666    pub fn as_datetime(&self) -> Option<DateTime> {
667        match self {
668            HeaderValue::DateTime(date) => Some(*date),
669            _ => None,
670        }
671    }
672
673    /// The Content-Type or Content-Disposition value.
674    pub fn as_content_type(&self) -> Option<ContentType<'m>> {
675        match self {
676            HeaderValue::ContentType(content_type) => Some(*content_type),
677            _ => None,
678        }
679    }
680
681    /// The Received trace field.
682    pub fn as_received(&self) -> Option<Received<'m>> {
683        match self {
684            HeaderValue::Received(received) => Some(*received),
685            _ => None,
686        }
687    }
688}
689
690impl<'m> TextList<'m> {
691    /// Number of items.
692    pub fn len(&self) -> usize {
693        self.items.len()
694    }
695
696    /// Whether the list has no item.
697    pub fn is_empty(&self) -> bool {
698        self.items.is_empty()
699    }
700
701    /// The item at `index`.
702    pub fn get(&self, index: usize) -> Option<&'m str> {
703        self.items.get(index).map(|item| self.resolver.str(*item))
704    }
705
706    /// The first item.
707    pub fn first(&self) -> Option<&'m str> {
708        self.items.first().map(|item| self.resolver.str(*item))
709    }
710
711    /// The last item.
712    pub fn last(&self) -> Option<&'m str> {
713        self.items.last().map(|item| self.resolver.str(*item))
714    }
715
716    /// Every item in order.
717    pub fn iter(&self) -> impl ExactSizeIterator<Item = &'m str> + DoubleEndedIterator + use<'m> {
718        let resolver = self.resolver;
719        self.items.iter().map(move |item| resolver.str(*item))
720    }
721
722    /// Whether an item equals `text` exactly.
723    pub fn contains(&self, text: &str) -> bool {
724        self.iter().any(|item| item == text)
725    }
726}
727
728impl fmt::Debug for Headers<'_> {
729    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
730        f.debug_list().entries(self.iter()).finish()
731    }
732}
733
734impl fmt::Debug for Header<'_> {
735    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
736        f.debug_struct("Header")
737            .field("name", &self.raw_name())
738            .field("value", &self.value())
739            .field("offset_field", &self.offset_field())
740            .field("offset_start", &self.offset_start())
741            .field("offset_end", &self.offset_end())
742            .finish()
743    }
744}
745
746impl fmt::Debug for HeaderIter<'_> {
747    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
748        f.debug_struct("HeaderIter")
749            .field("remaining", &self.entries.len())
750            .finish_non_exhaustive()
751    }
752}
753
754impl fmt::Debug for NamedHeaders<'_, '_> {
755    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
756        f.debug_struct("NamedHeaders")
757            .field("name", &self.name)
758            .finish_non_exhaustive()
759    }
760}
761
762impl fmt::Debug for TextList<'_> {
763    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
764        f.debug_list().entries(self.iter()).finish()
765    }
766}
767
768impl PartialEq for TextList<'_> {
769    fn eq(&self, other: &Self) -> bool {
770        self.items.len() == other.items.len() && self.iter().eq(other.iter())
771    }
772}
773
774impl Eq for TextList<'_> {}