Skip to main content

sipx_sip/headers/
address.rs

1//! Address headers: `From`, `To`, `Contact`, `Route`, `Record-Route`, `Refer-To`.
2//!
3//! All share one grammar (RFC 3261 §20.10, §20.20, §20.39):
4//!
5//! ```abnf
6//! ( name-addr / addr-spec ) *( SEMI generic-param )
7//! name-addr = [ display-name ] LAQUOT addr-spec RAQUOT
8//! ```
9//!
10//! The trap is the bare `addr-spec` form: without angle brackets, a semicolon starts a
11//! *header* parameter, not a URI parameter, so `sip:a@b;tag=1` is one URI and one header
12//! parameter — while `<sip:a@b;tag=1>` is one URI with a URI parameter and no header
13//! parameters. The two mean entirely different things and differ by two characters.
14
15use std::ops::Range;
16
17use bytes::Bytes;
18
19use crate::error::HeaderError;
20use crate::headers::grammar::{
21    self, HeaderParam, find_param_start, is_token_char, quoted_string_end, skip_ws, trim,
22};
23use crate::message::TypedHeader;
24use crate::name::HeaderName;
25use crate::uri::Uri;
26
27/// A display name with a URI and header parameters.
28#[derive(Debug, Clone)]
29pub struct Address {
30    /// The display name, unquoted and unescaped, if there was one.
31    pub display_name: Option<Vec<u8>>,
32    /// The URI.
33    pub uri: Uri,
34    /// The header parameters — those after the URI, outside any angle brackets.
35    pub params: Vec<HeaderParam>,
36}
37
38impl Address {
39    /// Parse one address.
40    pub fn parse(value: &[u8], header: &'static str) -> Result<Self, HeaderError> {
41        parse_spanned(value, header).map(|parsed| parsed.address)
42    }
43
44    /// Parse a header value carrying one or more comma-separated addresses.
45    ///
46    /// RFC 3261 §7.3: for `Contact`, `Route` and `Record-Route` a comma-joined row is
47    /// exactly equivalent to the same values on separate rows, so the row must be split
48    /// before the address grammar applies.
49    pub fn parse_list(value: &[u8], header: &'static str) -> Result<Vec<Self>, HeaderError> {
50        grammar::split_list(value, header)?
51            .into_iter()
52            .map(|part| Self::parse(part, header))
53            .collect()
54    }
55
56    /// The value of a header parameter.
57    #[must_use]
58    pub fn param(&self, name: &str) -> Option<&[u8]> {
59        grammar::param(&self.params, name).and_then(|p| p.value.as_deref())
60    }
61
62    /// The `tag` parameter, which identifies a dialog participant.
63    #[must_use]
64    pub fn tag(&self) -> Option<&[u8]> {
65        self.param("tag")
66    }
67}
68
69#[derive(Debug)]
70struct ParsedAddress {
71    address: Address,
72    presentation: Range<usize>,
73    uri: Range<usize>,
74}
75
76fn parse_spanned(value: &[u8], header: &'static str) -> Result<ParsedAddress, HeaderError> {
77    let outer = trimmed_range(value);
78    let value = value.get(outer.clone()).unwrap_or(&[]);
79    let mut i = skip_ws(value, 0);
80    let presentation_start = i;
81
82    // A display name is either a quoted string or a run of tokens; either way it ends at
83    // the '<' that opens the URI.
84    let mut display_name = None;
85    if value.get(i) == Some(&b'"') {
86        let end =
87            quoted_string_end(value, i).ok_or(HeaderError::UnterminatedQuotedString { header })?;
88        let raw = value.get(i + 1..end.saturating_sub(1)).unwrap_or(&[]);
89        display_name = Some(unescape(raw));
90        i = skip_ws(value, end);
91        if value.get(i) != Some(&b'<') {
92            return Err(HeaderError::Syntax { header });
93        }
94    } else if let Some(angle) = find_angle(value, i) {
95        let raw = trim(value.get(i..angle).unwrap_or(&[]));
96        if !raw.is_empty() {
97            // An unquoted display name is a sequence of tokens separated by whitespace.
98            // A comma here is not a token character, which is what makes
99            // `From: Bell, Alexander <sip:…>` invalid (RFC 4475 §3.1.2.15).
100            if !raw
101                .iter()
102                .all(|&b| is_token_char(b) || matches!(b, b' ' | b'\t'))
103            {
104                return Err(HeaderError::Syntax { header });
105            }
106            display_name = Some(raw.to_vec());
107        }
108        i = angle;
109    }
110
111    let (uri_span, presentation_span, params_tail) = if value.get(i) == Some(&b'<') {
112        let close = find_closing_angle(value, i).ok_or(HeaderError::Syntax { header })?;
113        (
114            i + 1..close,
115            presentation_start..close.saturating_add(1),
116            value.get(close + 1..).unwrap_or(&[]),
117        )
118    } else {
119        // Bare addr-spec: the URI runs to the first header-parameter semicolon.
120        let rest = value.get(i..).unwrap_or(&[]);
121        let param_start = find_param_start(rest);
122        let candidate = match param_start {
123            Some(semi) => i..i.checked_add(semi).ok_or(HeaderError::Syntax { header })?,
124            None => i..value.len(),
125        };
126        let candidate_bytes = value.get(candidate.clone()).unwrap_or(&[]);
127        let trimmed = trimmed_range(candidate_bytes);
128        let uri_span = candidate
129            .start
130            .checked_add(trimmed.start)
131            .zip(candidate.start.checked_add(trimmed.end))
132            .map(|(start, end)| start..end)
133            .ok_or(HeaderError::Syntax { header })?;
134        let params_tail = if let Some(semi) = param_start {
135            rest.get(semi..).unwrap_or(&[])
136        } else {
137            &[][..]
138        };
139        (uri_span.clone(), uri_span, params_tail)
140    };
141
142    let uri_bytes = value.get(uri_span.clone()).unwrap_or(&[]);
143    if uri_bytes.is_empty() {
144        return Err(HeaderError::Syntax { header });
145    }
146
147    // RFC 8217 applies to every `(name-addr / addr-spec)` choice, independent of URI scheme.
148    // A bare question mark is therefore malformed before scheme-specific URI parsing decides
149    // whether it represents a structured SIP header component or belongs to an opaque body.
150    if value.get(i) != Some(&b'<') && uri_bytes.contains(&b'?') {
151        return Err(HeaderError::Syntax { header });
152    }
153    let parsed_uri = Uri::parse(Bytes::copy_from_slice(uri_bytes))
154        .map_err(|source| HeaderError::Uri { header, source })?;
155
156    let params = grammar::parse_params(trim(params_tail), header)?;
157    let uri_span = add_offset(uri_span, outer.start, header)?;
158    let presentation_span = add_offset(presentation_span, outer.start, header)?;
159
160    Ok(ParsedAddress {
161        address: Address {
162            display_name,
163            uri: parsed_uri,
164            params,
165        },
166        presentation: presentation_span,
167        uri: uri_span,
168    })
169}
170
171/// Parser-owned ranges for one address-list value in an unfolded field value.
172#[derive(Debug, Clone)]
173pub(crate) struct AddressValueSpan {
174    /// The complete comma-delimited segment, including surrounding linear whitespace.
175    pub(crate) part: Range<usize>,
176    /// The address itself, excluding surrounding linear whitespace.
177    pub(crate) item: Range<usize>,
178    /// Display name, brackets and URI for name-address, or the URI for bare addr-spec.
179    pub(crate) presentation: Range<usize>,
180    /// The nested URI.
181    pub(crate) uri: Range<usize>,
182}
183
184/// Parse address values and retain their grammatical byte ranges.
185///
186/// The ordinary parser returns these ranges from the same pass that constructs [`Address`], so the
187/// editor cannot drift into a second permissive delimiter implementation.
188pub(crate) fn value_spans(
189    value: &[u8],
190    header: &'static str,
191    is_list: bool,
192) -> Result<Vec<AddressValueSpan>, HeaderError> {
193    let parts = if is_list {
194        grammar::split_list_spans(value, header)?
195    } else {
196        std::iter::once(0..value.len()).collect()
197    };
198
199    parts
200        .into_iter()
201        .map(|part| {
202            let bytes = value.get(part.clone()).unwrap_or(&[]);
203            let parsed = parse_spanned(bytes, header)?;
204            let item = trimmed_range(bytes);
205            Ok(AddressValueSpan {
206                part: part.clone(),
207                item: add_offset(item, part.start, header)?,
208                presentation: add_offset(parsed.presentation, part.start, header)?,
209                uri: add_offset(parsed.uri, part.start, header)?,
210            })
211        })
212        .collect()
213}
214
215fn trimmed_range(value: &[u8]) -> Range<usize> {
216    let mut start = 0usize;
217    while matches!(value.get(start), Some(b' ' | b'\t')) {
218        start += 1;
219    }
220    let mut end = value.len();
221    while end > start && matches!(value.get(end - 1), Some(b' ' | b'\t')) {
222        end -= 1;
223    }
224    start..end
225}
226
227fn add_offset(
228    range: Range<usize>,
229    offset: usize,
230    header: &'static str,
231) -> Result<Range<usize>, HeaderError> {
232    offset
233        .checked_add(range.start)
234        .zip(offset.checked_add(range.end))
235        .map(|(start, end)| start..end)
236        .ok_or(HeaderError::Syntax { header })
237}
238
239/// The index of the `<` that opens a URI, if the value uses the `name-addr` form.
240///
241/// Quoted strings are skipped, escapes and all: `qdtext` includes `%x3C` (RFC 3261 §25.1),
242/// so the `<` in `sip:a@b;x="<y>"` is parameter text, not the start of a name-addr.
243#[must_use]
244fn find_angle(value: &[u8], from: usize) -> Option<usize> {
245    let mut i = from;
246    while i < value.len() {
247        match value.get(i) {
248            Some(b'"') => i = quoted_string_end(value, i)?,
249            Some(b'<') => return Some(i),
250            Some(_) => i += 1,
251            None => break,
252        }
253    }
254    None
255}
256
257#[must_use]
258fn find_closing_angle(value: &[u8], open: usize) -> Option<usize> {
259    value
260        .get(open + 1..)?
261        .iter()
262        .position(|&b| b == b'>')
263        .map(|p| p + open + 1)
264}
265
266#[must_use]
267fn unescape(raw: &[u8]) -> Vec<u8> {
268    let mut out = Vec::with_capacity(raw.len());
269    let mut i = 0usize;
270    while let Some(&b) = raw.get(i) {
271        if b == b'\\'
272            && let Some(&next) = raw.get(i + 1)
273        {
274            out.push(next);
275            i += 2;
276            continue;
277        }
278        out.push(b);
279        i += 1;
280    }
281    out
282}
283
284macro_rules! address_type {
285    ($(#[$meta:meta])* $type:ident) => {
286        $(#[$meta])*
287        #[derive(Debug, Clone)]
288        pub struct $type(pub Address);
289
290        impl std::ops::Deref for $type {
291            type Target = Address;
292            fn deref(&self) -> &Address {
293                &self.0
294            }
295        }
296    };
297}
298
299/// A header holding exactly one address per row. A comma in the value is a fault, not a
300/// separator: `From` and `To` are single-value (RFC 3261 §20.20, §20.39).
301macro_rules! single_address_header {
302    ($(#[$meta:meta])* $type:ident => $variant:ident, $label:literal) => {
303        address_type!($(#[$meta])* $type);
304
305        impl TypedHeader for $type {
306            const NAME: HeaderName = HeaderName::$variant;
307
308            fn decode(value: &[u8]) -> Result<Self, HeaderError> {
309                Address::parse(value, $label).map(Self)
310            }
311        }
312    };
313}
314
315/// A header whose row may carry several comma-separated addresses (RFC 3261 §7.3).
316macro_rules! address_list_header {
317    ($(#[$meta:meta])* $type:ident => $variant:ident, $label:literal) => {
318        address_type!($(#[$meta])* $type);
319
320        impl $type {
321            /// Parse a header value that may carry several comma-separated addresses.
322            pub fn parse_list(value: &[u8]) -> Result<Vec<Self>, HeaderError> {
323                Address::parse_list(value, $label).map(|list| list.into_iter().map(Self).collect())
324            }
325        }
326
327        impl TypedHeader for $type {
328            const NAME: HeaderName = HeaderName::$variant;
329
330            /// Decodes the **first** address in the value; use [`Self::parse_list`] or
331            /// [`crate::message::Headers::typed_all`] when every one is needed.
332            fn decode(value: &[u8]) -> Result<Self, HeaderError> {
333                let parts = grammar::split_list(value, $label)?;
334                let first = parts.first().copied().unwrap_or(&[]);
335                Address::parse(first, $label).map(Self)
336            }
337
338            fn decode_list(value: &[u8]) -> Result<Vec<Self>, HeaderError> {
339                Self::parse_list(value)
340            }
341        }
342    };
343}
344
345single_address_header!(
346    /// The `From` header (RFC 3261 §20.20).
347    From => From, "From"
348);
349single_address_header!(
350    /// The `To` header (RFC 3261 §20.39).
351    To => To, "To"
352);
353address_list_header!(
354    /// The `Contact` header (RFC 3261 §20.10).
355    ///
356    /// A `Contact` of `*` is legal in a REGISTER and is *not* an address; parse it with
357    /// [`ContactValue`] rather than this type.
358    Contact => Contact, "Contact"
359);
360address_list_header!(
361    /// The `Route` header (RFC 3261 §20.34).
362    Route => Route, "Route"
363);
364address_list_header!(
365    /// The `Record-Route` header (RFC 3261 §20.30).
366    RecordRoute => RecordRoute, "Record-Route"
367);
368address_list_header!(
369    /// The `Path` header (RFC 3327 §4).
370    ///
371    /// A route header, not a `Contact`-shaped one, and it has to be parsed with list semantics
372    /// for the same reason `Record-Route` does: proxies each add their own value, and RFC 3261
373    /// §7.3 lets a comma-joined row stand for the same values on separate rows. Read a line at
374    /// a time, a two-proxy path becomes one opaque string and the order — which is the entire
375    /// content of a path vector — is lost.
376    Path => Path, "Path"
377);
378address_list_header!(
379    /// The `Service-Route` header (RFC 3608 §5).
380    ///
381    /// `sr-value = name-addr *( SEMI rr-param )`, comma-separated — the same list grammar the
382    /// other route headers have, and list semantics for the same reason: RFC 3608 §6.1 requires
383    /// a UA that exercises a service route to "preserve the order", and order is exactly what is
384    /// lost when a comma-joined row is read as one opaque value.
385    ServiceRoute => ServiceRoute, "Service-Route"
386);
387
388/// A `Contact` value, which may be the wildcard `*`.
389///
390/// RFC 3261 §10.2.2: `Contact: *` with `Expires: 0` deregisters everything. It is the one
391/// place in the grammar where a header that otherwise holds addresses holds a single asterisk
392/// instead, and a parser that expects an address there will reject a legal deregistration.
393#[derive(Debug, Clone)]
394pub enum ContactValue {
395    /// `*` — every registration.
396    Wildcard,
397    /// An ordinary address.
398    Address(Address),
399}
400
401impl TypedHeader for ContactValue {
402    const NAME: HeaderName = HeaderName::Contact;
403
404    /// Decodes the wildcard, or the **first** address in the value; use
405    /// [`TypedHeader::decode_list`] when every one is needed.
406    fn decode(value: &[u8]) -> Result<Self, HeaderError> {
407        if trim(value) == b"*" {
408            return Ok(Self::Wildcard);
409        }
410        Contact::decode(value).map(|c| Self::Address(c.0))
411    }
412
413    fn decode_list(value: &[u8]) -> Result<Vec<Self>, HeaderError> {
414        // The wildcard is the entire value: `Contact: *, <sip:a@b>` is not in the grammar,
415        // which has `STAR` as an alternative to the whole contact-param list (RFC 3261 §25.1).
416        if trim(value) == b"*" {
417            return Ok(vec![Self::Wildcard]);
418        }
419        Address::parse_list(value, "Contact")
420            .map(|list| list.into_iter().map(Self::Address).collect())
421    }
422}
423
424#[cfg(test)]
425#[allow(
426    clippy::unwrap_used,
427    clippy::expect_used,
428    clippy::panic,
429    clippy::indexing_slicing
430)]
431mod tests {
432    use super::*;
433
434    fn addr(value: &[u8]) -> Address {
435        Address::parse(value, "To").unwrap_or_else(|e| panic!("{value:?} should parse: {e}"))
436    }
437
438    #[test]
439    fn parses_a_bare_addr_spec() {
440        let a = addr(b"sip:j.user@example.com");
441        assert!(a.display_name.is_none());
442        assert_eq!(
443            a.uri.to_bytes(),
444            Bytes::from_static(b"sip:j.user@example.com")
445        );
446        assert!(a.params.is_empty());
447    }
448
449    #[test]
450    fn parses_a_quoted_display_name_with_escapes() {
451        // RFC 4475 3.1.1.1 carries exactly this: an escaped backslash and an escaped quote.
452        let a = addr(br#""J Rosenberg \\\"" <sip:jdrosen@example.com>;tag=98asjd8"#);
453        assert_eq!(a.display_name.as_deref(), Some(&br#"J Rosenberg \""#[..]));
454        assert_eq!(a.tag(), Some(&b"98asjd8"[..]));
455    }
456
457    #[test]
458    fn parses_an_unquoted_token_display_name() {
459        let a = addr(b"J Rosenberg <sip:jdrosen@example.com>");
460        assert_eq!(a.display_name.as_deref(), Some(&b"J Rosenberg"[..]));
461    }
462
463    /// RFC 4475 3.1.1.6: no whitespace between the display name and the `<`.
464    #[test]
465    fn parses_a_display_name_abutting_the_angle_bracket() {
466        let a = addr(br#""caller"<sip:caller@example.com>;tag=323"#);
467        assert_eq!(a.display_name.as_deref(), Some(&b"caller"[..]));
468        assert_eq!(a.tag(), Some(&b"323"[..]));
469    }
470
471    /// The distinction that costs two characters and changes everything.
472    #[test]
473    fn semicolons_bind_to_the_header_without_brackets_and_to_the_uri_within_them() {
474        let bare = addr(b"sip:a@b.com;tag=1");
475        assert_eq!(bare.uri.to_bytes(), Bytes::from_static(b"sip:a@b.com"));
476        assert_eq!(bare.tag(), Some(&b"1"[..]));
477
478        let bracketed = addr(b"<sip:a@b.com;tag=1>");
479        assert_eq!(
480            bracketed.uri.to_bytes(),
481            Bytes::from_static(b"sip:a@b.com;tag=1")
482        );
483        assert!(bracketed.tag().is_none());
484    }
485
486    /// RFC 3261 §25.1: `gen-value` may be a quoted string, and `qdtext` includes `<`
487    /// (%x3C), so an angle bracket inside a quoted parameter value never opens a name-addr.
488    #[test]
489    fn a_quoted_parameter_value_may_contain_an_angle_bracket() {
490        let a = addr(br#"sip:a@b.com;x="<y>""#);
491        assert_eq!(a.uri.to_bytes(), Bytes::from_static(b"sip:a@b.com"));
492        assert_eq!(a.param("x"), Some(&b"<y>"[..]));
493
494        let a = addr(br#"sip:a@b.com;note="hi <there>""#);
495        assert_eq!(a.uri.to_bytes(), Bytes::from_static(b"sip:a@b.com"));
496        assert_eq!(a.param("note"), Some(&b"hi <there>"[..]));
497
498        // An escaped quote does not end the string early.
499        let a = addr(br#"sip:a@b.com;x="a\"<b""#);
500        assert_eq!(a.param("x"), Some(&br#"a"<b"#[..]));
501    }
502
503    /// RFC 4475 3.1.2.15. The archive file for this case is unterminated so the corpus test
504    /// cannot reach it; this is the hand-built version.
505    #[test]
506    fn rejects_an_unquoted_comma_in_a_display_name() {
507        let err = Address::parse(b"Bell, Alexander <sip:a.g.bell@example.com>", "From");
508        assert!(matches!(err, Err(HeaderError::Syntax { header: "From" })));
509    }
510
511    /// RFC 4475 3.1.2.6.
512    #[test]
513    fn rejects_an_unterminated_quoted_display_name() {
514        let err = Address::parse(br#""Mr. J. User <sip:j.user@example.com>"#, "To");
515        assert!(matches!(
516            err,
517            Err(HeaderError::UnterminatedQuotedString { header: "To" })
518        ));
519    }
520
521    /// RFC 4475 3.1.2.14: spaces inside the angle brackets are not part of any URI.
522    #[test]
523    fn rejects_spaces_within_the_addr_spec() {
524        let err = Address::parse(br#""Watson, Thomas" < sip:t.watson@example.org >"#, "To");
525        assert!(matches!(err, Err(HeaderError::Uri { .. })));
526    }
527
528    /// RFC 8217: a question mark requires name-addr for every URI scheme, or there is no way to
529    /// tell where the URI ends.
530    #[test]
531    fn rejects_an_unbracketed_question_mark_for_every_uri_scheme() {
532        for bare in [
533            b"sip:user@example.com?Route=%3Csip:sip.example.com%3E".as_slice(),
534            b"tel:+12015550123?x=y",
535            b"mailto:alice@example.com?subject=hello",
536        ] {
537            assert!(matches!(
538                Address::parse(bare, "Contact"),
539                Err(HeaderError::Syntax { header: "Contact" })
540            ));
541        }
542
543        // In brackets syntactically valid SIP and opaque URIs are fine.
544        for bracketed in [
545            b"<sip:user@example.com?Route=%3Csip:sip.example.com%3E>".as_slice(),
546            b"<mailto:alice@example.com?subject=hello>",
547        ] {
548            assert!(Address::parse(bracketed, "Contact").is_ok());
549        }
550    }
551
552    /// RFC 3261 §7.3: a comma-joined row is exactly equivalent to the same values on
553    /// separate rows, so the typed readers must accept a list.
554    #[test]
555    fn decodes_the_first_address_of_a_comma_separated_row() {
556        let c = Contact::decode(b"<sip:a@b.com>, <sip:c@d.com>").unwrap();
557        assert_eq!(c.uri.to_bytes(), Bytes::from_static(b"sip:a@b.com"));
558
559        let r = Route::decode(b"<sip:p1.example.com;lr>,<sip:p2.example.com;lr>").unwrap();
560        assert_eq!(
561            r.uri.to_bytes(),
562            Bytes::from_static(b"sip:p1.example.com;lr")
563        );
564    }
565
566    #[test]
567    fn parses_every_address_of_a_comma_separated_row() {
568        let list = Address::parse_list(
569            br#""Bell, Alexander" <sip:a@b.com>;q=0.7, <sip:c@d.com>"#,
570            "Contact",
571        )
572        .unwrap();
573        assert_eq!(list.len(), 2);
574        assert_eq!(
575            list[0].display_name.as_deref(),
576            Some(&b"Bell, Alexander"[..])
577        );
578        assert_eq!(list[0].param("q"), Some(&b"0.7"[..]));
579        assert_eq!(list[1].uri.to_bytes(), Bytes::from_static(b"sip:c@d.com"));
580
581        // One bad element spoils the row: the list is only as good as its members.
582        assert!(Address::parse_list(b"<sip:a@b.com>, not a uri", "Contact").is_err());
583    }
584
585    #[test]
586    fn wildcard_and_addresses_both_come_out_of_a_contact_list() {
587        let all = ContactValue::decode_list(b"*").unwrap();
588        assert!(matches!(all.as_slice(), [ContactValue::Wildcard]));
589
590        let all = ContactValue::decode_list(b"<sip:a@b.com>, <sip:c@d.com>").unwrap();
591        assert_eq!(all.len(), 2);
592        assert!(all.iter().all(|v| matches!(v, ContactValue::Address(_))));
593    }
594
595    #[test]
596    fn parses_the_wildcard_contact() {
597        assert!(matches!(
598            ContactValue::decode(b"*"),
599            Ok(ContactValue::Wildcard)
600        ));
601        assert!(matches!(
602            ContactValue::decode(b"<sip:a@b>"),
603            Ok(ContactValue::Address(_))
604        ));
605    }
606}