Skip to main content

sipx_sip/
uri.rs

1//! SIP, SIPS and other URIs (RFC 3261 §19.1).
2
3use std::fmt;
4use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
5
6use bytes::Bytes;
7use thiserror::Error;
8
9use crate::error::{BuildError, UriError};
10use crate::escape;
11use crate::params::{Param, Params};
12
13/// A URI scheme.
14///
15/// Comparison is case-insensitive, but `sip` and `sips` are **never** equivalent
16/// (RFC 3261 §19.1.4) — a secure URI is a different address, not a spelling variant.
17#[derive(Debug, Clone)]
18pub enum Scheme {
19    /// `sip:`
20    Sip,
21    /// `sips:`
22    Sips,
23    /// `tel:` (RFC 3966).
24    Tel,
25    /// Any other scheme, retained verbatim.
26    Other(Bytes),
27}
28
29/// Effective wire transport selected by a SIP/SIPS URI.
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum UriTransport {
32    /// UDP datagrams.
33    Udp,
34    /// TCP stream.
35    Tcp,
36    /// TLS over TCP.
37    Tls,
38    /// SIP over WebSocket.
39    Ws,
40    /// SIP over secure WebSocket.
41    Wss,
42    /// SIP over QUIC.
43    Quic,
44}
45
46impl UriTransport {
47    /// Default port for this transport when the URI omits one.
48    #[must_use]
49    pub fn default_port(self) -> u16 {
50        match self {
51            Self::Udp | Self::Tcp => 5060,
52            Self::Tls | Self::Quic => 5061,
53            Self::Ws => 80,
54            Self::Wss => 443,
55        }
56    }
57}
58
59/// Why a URI cannot select a safe wire transport.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
61#[non_exhaustive]
62pub enum UriTransportError {
63    /// The scheme is neither SIP nor SIPS.
64    #[error("the URI is not a SIP or SIPS URI")]
65    NotSip,
66    /// The transport parameter is not implemented.
67    #[error("the URI names an unsupported transport")]
68    Unsupported,
69    /// SIPS cannot be carried over UDP.
70    #[error("a SIPS URI cannot select UDP")]
71    SecureDatagram,
72}
73
74impl Scheme {
75    #[must_use]
76    fn parse(raw: &Bytes) -> Self {
77        if escape::eq_ignore_ascii_case(raw, b"sip") {
78            Self::Sip
79        } else if escape::eq_ignore_ascii_case(raw, b"sips") {
80            Self::Sips
81        } else if escape::eq_ignore_ascii_case(raw, b"tel") {
82            Self::Tel
83        } else {
84            Self::Other(raw.clone())
85        }
86    }
87
88    /// The scheme as it should be written.
89    #[must_use]
90    pub fn as_bytes(&self) -> &[u8] {
91        match self {
92            Self::Sip => b"sip",
93            Self::Sips => b"sips",
94            Self::Tel => b"tel",
95            Self::Other(raw) => raw,
96        }
97    }
98
99    /// Whether this scheme implies a secure transport.
100    #[must_use]
101    pub fn is_secure(&self) -> bool {
102        matches!(self, Self::Sips)
103    }
104
105    /// Whether this is `sip:` or `sips:`, and therefore has structured parts.
106    #[must_use]
107    pub fn is_sip(&self) -> bool {
108        matches!(self, Self::Sip | Self::Sips)
109    }
110
111    #[must_use]
112    fn equivalent(&self, other: &Self) -> bool {
113        escape::eq_ignore_ascii_case(self.as_bytes(), other.as_bytes())
114    }
115}
116
117/// A validated hostname.
118///
119/// The inner bytes are private and the only public constructor checks them. That is what
120/// stops a caller from putting a CRLF in a host and injecting a header through the
121/// Request-URI: without this, `Host::Name(b"evil\r\nInjected: yes")` would serialize into a
122/// perfectly convincing forged request line.
123#[derive(Debug, Clone)]
124pub struct HostName(Bytes);
125
126impl HostName {
127    /// Validate a hostname.
128    pub fn new(name: impl Into<Bytes>) -> Result<Self, BuildError> {
129        let name = name.into();
130        if name.is_empty() || !name.iter().all(|&b| is_host_char(b)) {
131            return Err(BuildError::NotAToken { field: "host" });
132        }
133        Ok(Self(name))
134    }
135
136    pub(crate) fn new_unchecked(name: Bytes) -> Self {
137        Self(name)
138    }
139
140    /// The hostname.
141    #[must_use]
142    pub fn as_bytes(&self) -> &[u8] {
143        &self.0
144    }
145}
146
147impl PartialEq<&[u8]> for HostName {
148    fn eq(&self, other: &&[u8]) -> bool {
149        self.0 == *other
150    }
151}
152
153impl PartialEq<&str> for HostName {
154    fn eq(&self, other: &&str) -> bool {
155        self.0 == other.as_bytes()
156    }
157}
158
159/// The host part of a URI.
160#[derive(Debug, Clone)]
161pub enum Host {
162    /// A hostname.
163    Name(HostName),
164    /// A literal IPv4 or IPv6 address.
165    Ip(IpAddr),
166}
167
168impl Host {
169    /// Parse a `host [ ":" port ]`, as it appears in a URI or in a `Via` sent-by.
170    ///
171    /// Shared with the `Via` header so that a hostname is validated the same way wherever it
172    /// appears; a host that is rejected in a URI must not be accepted in a `Via`.
173    pub fn parse_hostport(raw: &Bytes) -> Result<(Self, Option<u16>), UriError> {
174        parse_hostport(raw)
175    }
176
177    /// The host as it should be written, without IPv6 brackets.
178    #[must_use]
179    pub fn to_bytes(&self) -> Bytes {
180        match self {
181            Self::Name(name) => name.0.clone(),
182            Self::Ip(ip) => Bytes::from(ip.to_string()),
183        }
184    }
185
186    /// Whether two hosts are the same.
187    ///
188    /// Hostnames compare case-insensitively. A hostname never matches an IP address, even the
189    /// one it resolves to — RFC 3261 §19.1.4 is explicit, and a comparison that consulted DNS
190    /// would be neither pure nor stable.
191    #[must_use]
192    pub fn equivalent(&self, other: &Self) -> bool {
193        match (self, other) {
194            (Self::Ip(a), Self::Ip(b)) => a == b,
195            (Self::Name(a), Self::Name(b)) => escape::eq_ignore_ascii_case(&a.0, &b.0),
196            _ => false,
197        }
198    }
199}
200
201impl fmt::Display for Host {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        match self {
204            Self::Name(name) => write!(f, "{}", String::from_utf8_lossy(&name.0)),
205            Self::Ip(IpAddr::V6(ip)) => write!(f, "[{ip}]"),
206            Self::Ip(ip) => write!(f, "{ip}"),
207        }
208    }
209}
210
211/// The structured parts of a `sip:` or `sips:` URI.
212#[derive(Debug, Clone)]
213struct SipParts {
214    user: Option<Bytes>,
215    /// Exact span of `user` in [`Uri::raw`]. Absent for a URI without userinfo and cleared when
216    /// another structured mutation discards the verbatim form.
217    raw_user_span: Option<std::ops::Range<usize>>,
218    password: Option<Bytes>,
219    host: Host,
220    port: Option<u16>,
221    params: Params,
222    headers: Params,
223}
224
225#[derive(Debug, Clone)]
226enum Parts {
227    Sip(Box<SipParts>),
228    /// Everything after the scheme, for schemes sipx does not model.
229    Opaque(Bytes),
230}
231
232/// Borrowed syntax parts of an RFC 3966 `tel:` URI.
233///
234/// The view is deliberately byte-oriented and lossless. It neither removes visual separators
235/// nor interprets parameters such as `phone-context`; those are policy decisions for the caller.
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub struct TelUriParts<'a> {
238    subscriber: &'a [u8],
239    parameters: Option<&'a [u8]>,
240}
241
242impl<'a> TelUriParts<'a> {
243    /// The exact `telephone-subscriber` bytes before the first `;`.
244    #[must_use]
245    pub fn subscriber(&self) -> &'a [u8] {
246        self.subscriber
247    }
248
249    /// The exact parameter tail after the first `;`, without that delimiter.
250    ///
251    /// `None` means there was no delimiter. `Some(b"")` retains a trailing delimiter from an
252    /// opaque URI body, even though an empty parameter is not valid RFC 3966 syntax.
253    #[must_use]
254    pub fn parameters(&self) -> Option<&'a [u8]> {
255        self.parameters
256    }
257
258    /// Iterate the exact generic RFC 3966 parameters with structural validation.
259    ///
260    /// Names compare case-insensitively through [`TelParameter::name_eq`]. Order, duplicates,
261    /// percent escapes and original spelling are retained; parameter-specific policy is not
262    /// applied. A malformed item is yielded once and fuses the iterator.
263    #[must_use]
264    pub fn parsed_parameters(&self) -> TelParameters<'a> {
265        TelParameters {
266            remaining: self.parameters,
267            tail_len: self.parameters.map_or(0, <[u8]>::len),
268        }
269    }
270}
271
272/// Allocation-free iterator over one TEL URI's retained parameter tail.
273#[derive(Debug, Clone)]
274pub struct TelParameters<'a> {
275    remaining: Option<&'a [u8]>,
276    tail_len: usize,
277}
278
279/// One structurally valid generic RFC 3966 parameter.
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
281pub struct TelParameter<'a> {
282    name: &'a [u8],
283    value: Option<&'a [u8]>,
284}
285
286impl<'a> TelParameter<'a> {
287    /// The exact parameter-name bytes.
288    #[must_use]
289    pub fn name(&self) -> &'a [u8] {
290        self.name
291    }
292
293    /// The exact parameter value, or `None` when the wire parameter had no `=` delimiter.
294    #[must_use]
295    pub fn value(&self) -> Option<&'a [u8]> {
296        self.value
297    }
298
299    /// Compare a valid RFC 3966 parameter name with ASCII case folding.
300    ///
301    /// An empty or syntactically invalid candidate is never equal.
302    #[must_use]
303    pub fn name_eq(&self, expected: &[u8]) -> bool {
304        valid_tel_parameter_name(expected) && escape::eq_ignore_ascii_case(self.name, expected)
305    }
306}
307
308/// Why one retained TEL parameter tail is not structurally valid.
309#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
310#[error("invalid TEL parameter at tail byte {offset}: {kind}")]
311pub struct TelParameterError {
312    offset: usize,
313    kind: TelParameterErrorKind,
314}
315
316impl TelParameterError {
317    /// Tail-relative byte offset of the offending component or byte.
318    #[must_use]
319    pub fn offset(&self) -> usize {
320        self.offset
321    }
322
323    /// The rejected grammar component.
324    #[must_use]
325    pub fn kind(&self) -> TelParameterErrorKind {
326        self.kind
327    }
328}
329
330/// The malformed part of a generic RFC 3966 parameter.
331#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
332#[non_exhaustive]
333pub enum TelParameterErrorKind {
334    /// An empty segment, including a trailing or repeated `;` delimiter.
335    #[error("empty parameter")]
336    Empty,
337    /// An empty or syntactically invalid `pname`.
338    #[error("invalid parameter name")]
339    Name,
340    /// An empty or syntactically invalid `pvalue`.
341    #[error("invalid parameter value")]
342    Value,
343}
344
345impl<'a> Iterator for TelParameters<'a> {
346    type Item = Result<TelParameter<'a>, TelParameterError>;
347
348    fn next(&mut self) -> Option<Self::Item> {
349        let remaining = self.remaining.take()?;
350        let offset = self.tail_len.saturating_sub(remaining.len());
351        let (segment, rest) = match remaining.iter().position(|&byte| byte == b';') {
352            Some(separator) => (
353                remaining.get(..separator).unwrap_or(&[]),
354                remaining.get(separator.saturating_add(1)..),
355            ),
356            None => (remaining, None),
357        };
358        self.remaining = rest;
359
360        match parse_tel_parameter(segment, offset) {
361            Ok(parameter) => Some(Ok(parameter)),
362            Err(error) => {
363                self.remaining = None;
364                Some(Err(error))
365            }
366        }
367    }
368}
369
370impl std::iter::FusedIterator for TelParameters<'_> {}
371
372/// A URI.
373///
374/// # Equality
375///
376/// [`Uri`] deliberately does **not** implement `PartialEq` as RFC 3261 equivalence. That
377/// relation is not transitive — the RFC says so in §19.1.4, and gives the example that
378/// `sip:carol@chicago.com` is equivalent to both `sip:carol@chicago.com;security=on` and
379/// `sip:carol@chicago.com;security=off`, which are not equivalent to each other. A
380/// non-transitive `PartialEq` breaks `HashMap`, sorting, and every reader's assumptions.
381///
382/// So: [`Uri::equivalent`] implements the RFC relation and is what protocol logic must use.
383#[derive(Debug, Clone)]
384pub struct Uri {
385    scheme: Scheme,
386    parts: Parts,
387    /// The exact wire form this URI last retained, so an untouched or span-rewritten URI is
388    /// emitted without disturbing unrelated spelling. `None` for a constructed URI or after a
389    /// general structured mutation.
390    raw: Option<Bytes>,
391    /// Exact span of an RFC 3966 telephone-subscriber in [`Self::raw`]. Present only for a
392    /// parsed `tel:` URI, whose opaque body otherwise deliberately stays unmodelled.
393    raw_tel_subscriber_span: Option<std::ops::Range<usize>>,
394}
395
396impl Uri {
397    /// Parse a URI.
398    ///
399    /// The input must be the URI alone: any enclosing `<>` and surrounding whitespace belong
400    /// to the header grammar and must be stripped by the caller.
401    pub fn parse(raw: Bytes) -> Result<Self, UriError> {
402        for &b in &raw {
403            // The URI grammar is printable US-ASCII. Rejecting whitespace here is what makes
404            // RFC 4475 3.1.2.8 (embedded LWS in a Request-URI) a parse failure rather than a
405            // silently truncated host.
406            if !(0x21..=0x7e).contains(&b) || matches!(b, b'<' | b'>' | b'"') {
407                return Err(UriError::IllegalCharacter);
408            }
409        }
410
411        let colon = raw
412            .iter()
413            .position(|&b| b == b':')
414            .ok_or(UriError::Scheme)?;
415        let scheme_raw = raw.slice(..colon);
416        if scheme_raw.is_empty() || !scheme_raw.iter().all(|&b| is_scheme_char(b)) {
417            return Err(UriError::Scheme);
418        }
419        let scheme = Scheme::parse(&scheme_raw);
420        let rest = raw.slice(colon + 1..);
421        if !escape::escapes_are_well_formed(&rest) {
422            return Err(UriError::PercentEscape);
423        }
424
425        let raw_tel_subscriber_span = if matches!(scheme, Scheme::Tel) {
426            let body_offset = colon.checked_add(1).ok_or(UriError::TelephoneSubscriber)?;
427            let subscriber = split_tel_body(&rest).subscriber;
428            validate_tel_subscriber(subscriber)?;
429            let subscriber_len = subscriber.len();
430            let end = body_offset
431                .checked_add(subscriber_len)
432                .ok_or(UriError::TelephoneSubscriber)?;
433            Some(body_offset..end)
434        } else {
435            None
436        };
437
438        let parts = if scheme.is_sip() {
439            Parts::Sip(Box::new(parse_sip_parts(&rest, colon + 1)?))
440        } else {
441            Parts::Opaque(rest)
442        };
443
444        Ok(Self {
445            scheme,
446            parts,
447            raw: Some(raw),
448            raw_tel_subscriber_span,
449        })
450    }
451
452    /// Build a `sip:` or `sips:` URI.
453    #[must_use]
454    pub fn sip(host: Host) -> Self {
455        Self {
456            scheme: Scheme::Sip,
457            parts: Parts::Sip(Box::new(SipParts {
458                user: None,
459                raw_user_span: None,
460                password: None,
461                host,
462                port: None,
463                params: Params::new(),
464                headers: Params::new(),
465            })),
466            raw: None,
467            raw_tel_subscriber_span: None,
468        }
469    }
470
471    /// The scheme.
472    #[must_use]
473    pub fn scheme(&self) -> &Scheme {
474        &self.scheme
475    }
476
477    #[must_use]
478    fn sip_parts(&self) -> Option<&SipParts> {
479        match &self.parts {
480            Parts::Sip(p) => Some(p),
481            Parts::Opaque(_) => None,
482        }
483    }
484
485    #[must_use]
486    fn sip_parts_mut(&mut self) -> Option<&mut SipParts> {
487        match &mut self.parts {
488            Parts::Sip(p) => {
489                // Any general structured mutation invalidates the verbatim form and therefore
490                // its retained user span. `replace_user` is the one operation that can update
491                // both losslessly, so it does not enter through this helper.
492                self.raw = None;
493                p.raw_user_span = None;
494                Some(p)
495            }
496            Parts::Opaque(_) => None,
497        }
498    }
499
500    /// The user part, still percent-encoded, or `None` for a URI with no userinfo or a
501    /// scheme sipx does not model.
502    #[must_use]
503    pub fn user(&self) -> Option<&[u8]> {
504        self.sip_parts().and_then(|p| p.user.as_deref())
505    }
506
507    /// The password, still percent-encoded.
508    ///
509    /// Present in the grammar and therefore parsed; RFC 3261 §19.1.1 advises against using
510    /// it, and sipx never puts one in a URI it builds.
511    #[must_use]
512    pub fn password(&self) -> Option<&[u8]> {
513        self.sip_parts().and_then(|p| p.password.as_deref())
514    }
515
516    /// The user part with its percent escapes decoded.
517    ///
518    /// Yields bytes, not a string, and that is not an oversight: RFC 4475 §3.1.1.4 has a
519    /// registration whose user part is `null-%00-null`, and `sip:%C3%A9@host` decodes to
520    /// non-ASCII. Either would have to panic or be lossily replaced to become a `str`.
521    ///
522    /// Returns `None` if there is no user part or an escape is malformed.
523    #[must_use]
524    pub fn decoded_user(&self) -> Option<Vec<u8>> {
525        self.user().and_then(escape::decode)
526    }
527
528    /// Replace an existing, already percent-encoded user part of a SIP or SIPS URI.
529    ///
530    /// Returns `Ok(false)` without touching the URI when its scheme is not SIP or SIPS or it has
531    /// no user part. For a parsed URI, a valid replacement changes only the retained user span:
532    /// scheme spelling, password, host spelling, delimiters, port, parameters and URI headers stay
533    /// byte-identical. The old verbatim form is invalidated rather than replayed stale. A URI whose
534    /// verbatim form was already discarded serializes canonically from its structured parts.
535    ///
536    /// # Errors
537    ///
538    /// [`UriError::PercentEscape`] reports a malformed `% HEX HEX` sequence. [`UriError::User`]
539    /// reports an empty value or a byte outside RFC 3261 §25.1's `user` production. Either error
540    /// leaves the URI unchanged.
541    pub fn replace_user(&mut self, user: impl Into<Bytes>) -> Result<bool, UriError> {
542        if !self.scheme.is_sip() {
543            return Ok(false);
544        }
545
546        let (raw, parts) = (&mut self.raw, &mut self.parts);
547        let Parts::Sip(parts) = parts else {
548            return Ok(false);
549        };
550        if parts.user.is_none() {
551            return Ok(false);
552        }
553        let user = user.into();
554        validate_user(&user)?;
555
556        let rewritten = match (raw.as_ref(), parts.raw_user_span.as_ref()) {
557            (Some(verbatim), Some(span)) => {
558                let end = span.start.checked_add(user.len()).ok_or(UriError::User)?;
559                let value = replace_raw_span(verbatim, span, &user).ok_or(UriError::User)?;
560                Some((value, span.start..end))
561            }
562            (Some(_), None) => return Err(UriError::User),
563            (None, _) => None,
564        };
565
566        parts.user = Some(user);
567        if let Some((value, span)) = rewritten {
568            *raw = Some(value);
569            parts.raw_user_span = Some(span);
570        } else {
571            *raw = None;
572            parts.raw_user_span = None;
573        }
574        Ok(true)
575    }
576
577    /// Replace the telephone-subscriber of a parsed RFC 3966 `tel:` URI.
578    ///
579    /// Returns `Ok(false)` without touching the URI for every other scheme. A successful
580    /// replacement splices only the parser-retained subscriber span, so mixed-case scheme
581    /// spelling and the complete optional parameter tail stay byte-identical. This validates
582    /// the global/local subscriber production but deliberately does not interpret parameters
583    /// such as `phone-context`.
584    ///
585    /// # Errors
586    ///
587    /// [`UriError::TelephoneSubscriber`] reports an empty value or one outside RFC 3966's
588    /// `global-number-digits` and `local-number-digits` productions. The error is atomic.
589    pub fn replace_tel_subscriber(
590        &mut self,
591        subscriber: impl Into<Bytes>,
592    ) -> Result<bool, UriError> {
593        if !matches!(self.scheme, Scheme::Tel) {
594            return Ok(false);
595        }
596
597        let subscriber = subscriber.into();
598        validate_tel_subscriber(&subscriber)?;
599
600        let (raw, parts, span) = (
601            &mut self.raw,
602            &mut self.parts,
603            &mut self.raw_tel_subscriber_span,
604        );
605        let Parts::Opaque(body) = parts else {
606            return Err(UriError::TelephoneSubscriber);
607        };
608        let (Some(verbatim), Some(current_span)) = (raw.as_ref(), span.as_ref()) else {
609            return Err(UriError::TelephoneSubscriber);
610        };
611        let start = current_span.start;
612        let end = start
613            .checked_add(subscriber.len())
614            .ok_or(UriError::TelephoneSubscriber)?;
615        let rewritten = replace_raw_span(verbatim, current_span, &subscriber)
616            .ok_or(UriError::TelephoneSubscriber)?;
617        if rewritten.get(start..).is_none() {
618            return Err(UriError::TelephoneSubscriber);
619        }
620        let mut rewritten_body = rewritten.clone();
621        let rewritten_body = rewritten_body.split_off(start);
622
623        *body = rewritten_body;
624        *raw = Some(rewritten);
625        *span = Some(start..end);
626        Ok(true)
627    }
628
629    /// The host.
630    #[must_use]
631    pub fn host(&self) -> Option<&Host> {
632        self.sip_parts().map(|p| &p.host)
633    }
634
635    /// The port, if the URI states one.
636    ///
637    /// A URI without a port is not the same as one naming the default port; see
638    /// [`Uri::equivalent`].
639    #[must_use]
640    pub fn port(&self) -> Option<u16> {
641        self.sip_parts().and_then(|p| p.port)
642    }
643
644    /// The URI parameters — the `;name=value` list.
645    #[must_use]
646    pub fn params(&self) -> Option<&Params> {
647        self.sip_parts().map(|p| &p.params)
648    }
649
650    /// The URI headers — the `?name=value&…` list.
651    #[must_use]
652    pub fn headers(&self) -> Option<&Params> {
653        self.sip_parts().map(|p| &p.headers)
654    }
655
656    /// Everything after the scheme, for a scheme sipx does not model.
657    #[must_use]
658    pub fn opaque(&self) -> Option<&[u8]> {
659        match &self.parts {
660            Parts::Opaque(body) => Some(body),
661            Parts::Sip(_) => None,
662        }
663    }
664
665    /// Split an RFC 3966 `tel:` URI into exact subscriber and parameter-tail spans.
666    ///
667    /// Returns `None` for every other scheme. This is a syntax view only: it preserves visual
668    /// separators, parameter spelling and order and performs no normalization or validation.
669    #[must_use]
670    pub fn tel_parts(&self) -> Option<TelUriParts<'_>> {
671        match (&self.scheme, &self.parts) {
672            (Scheme::Tel, Parts::Opaque(body)) => Some(split_tel_body(body)),
673            _ => None,
674        }
675    }
676
677    /// The value of the `transport` parameter.
678    #[must_use]
679    pub fn transport(&self) -> Option<&[u8]> {
680        self.params().and_then(|p| p.value("transport"))
681    }
682
683    /// Select the effective transport without resolving the URI's host.
684    pub fn selected_transport(&self) -> Result<UriTransport, UriTransportError> {
685        if !self.scheme.is_sip() {
686            return Err(UriTransportError::NotSip);
687        }
688        let explicit = match self.transport().map(<[u8]>::to_ascii_lowercase) {
689            None => None,
690            Some(value) => Some(match value.as_slice() {
691                b"udp" => UriTransport::Udp,
692                b"tcp" => UriTransport::Tcp,
693                b"tls" => UriTransport::Tls,
694                b"ws" => UriTransport::Ws,
695                b"wss" => UriTransport::Wss,
696                b"quic" => UriTransport::Quic,
697                _ => return Err(UriTransportError::Unsupported),
698            }),
699        };
700        if !self.scheme.is_secure() {
701            return Ok(explicit.unwrap_or(UriTransport::Udp));
702        }
703        match explicit {
704            None | Some(UriTransport::Tcp | UriTransport::Tls) => Ok(UriTransport::Tls),
705            Some(UriTransport::Ws | UriTransport::Wss) => Ok(UriTransport::Wss),
706            Some(UriTransport::Quic) => Ok(UriTransport::Quic),
707            Some(UriTransport::Udp) => Err(UriTransportError::SecureDatagram),
708        }
709    }
710
711    /// Add a URI parameter.
712    ///
713    /// Appended, not replaced: RFC 3261 §19.1.1 forbids a repeated `uri-parameter`, so a caller
714    /// re-setting one of its own parameters wants [`Uri::remove_param`] first — see [`Params`].
715    pub fn push_param(&mut self, param: Param) {
716        if let Some(parts) = self.sip_parts_mut() {
717            parts.params.push(param);
718        }
719    }
720
721    /// Add a URI header component and report whether this URI can carry one.
722    ///
723    /// SIP and SIPS URIs have a `?name=value` component. Opaque schemes, including `tel`, do
724    /// not; returning `false` lets History-Info follow RFC 7044 §10.2 without pretending a
725    /// reason was embedded in a URI whose grammar has nowhere to put it.
726    pub fn push_header(&mut self, header: Param) -> bool {
727        let Some(parts) = self.sip_parts_mut() else {
728            return false;
729        };
730        parts.headers.push(header);
731        true
732    }
733
734    /// Remove every URI header component with this name.
735    pub fn remove_header(&mut self, name: &str) -> bool {
736        self.sip_parts_mut()
737            .is_some_and(|parts| parts.headers.remove(name))
738    }
739
740    /// Remove a URI parameter, and say whether one was there.
741    ///
742    /// Names match the way §19.1.4 compares them, so `%74ransport` is `transport`.
743    pub fn remove_param(&mut self, name: &str) -> bool {
744        self.sip_parts_mut()
745            .is_some_and(|parts| parts.params.remove(name))
746    }
747
748    /// Whether this URI carries any header components.
749    ///
750    /// A Request-URI must not (RFC 3261 §19.1.1); validation uses this.
751    #[must_use]
752    pub fn has_headers(&self) -> bool {
753        self.headers().is_some_and(|h| !h.is_empty())
754    }
755
756    /// Serialize.
757    ///
758    /// A parsed, unmodified URI is written back exactly as it arrived.
759    pub fn write_to(&self, out: &mut Vec<u8>) {
760        if let Some(raw) = &self.raw {
761            out.extend_from_slice(raw);
762            return;
763        }
764        out.extend_from_slice(self.scheme.as_bytes());
765        out.push(b':');
766        match &self.parts {
767            Parts::Opaque(body) => out.extend_from_slice(body),
768            Parts::Sip(p) => {
769                if let Some(user) = &p.user {
770                    out.extend_from_slice(user);
771                    if let Some(password) = &p.password {
772                        out.push(b':');
773                        out.extend_from_slice(password);
774                    }
775                    out.push(b'@');
776                }
777                match &p.host {
778                    Host::Ip(IpAddr::V6(ip)) => {
779                        out.push(b'[');
780                        out.extend_from_slice(ip.to_string().as_bytes());
781                        out.push(b']');
782                    }
783                    host => out.extend_from_slice(&host.to_bytes()),
784                }
785                if let Some(port) = p.port {
786                    out.push(b':');
787                    out.extend_from_slice(port.to_string().as_bytes());
788                }
789                p.params.write_to(out, b';');
790                p.headers.write_to(out, b'?');
791            }
792        }
793    }
794
795    /// Serialize to bytes.
796    #[must_use]
797    pub fn to_bytes(&self) -> Bytes {
798        let mut out = Vec::new();
799        self.write_to(&mut out);
800        Bytes::from(out)
801    }
802
803    /// Whether two URIs are equivalent under RFC 3261 §19.1.4.
804    ///
805    /// Note that this relation is **not transitive**; see the type-level documentation.
806    #[must_use]
807    pub fn equivalent(&self, other: &Self) -> bool {
808        // "A SIP and SIPS URI are never equivalent."
809        if !self.scheme.equivalent(&other.scheme) {
810            return false;
811        }
812
813        let (a, b) = match (&self.parts, &other.parts) {
814            (Parts::Sip(a), Parts::Sip(b)) => (a, b),
815            (Parts::Opaque(a), Parts::Opaque(b)) => {
816                // A tel URI has its own equivalence rules (RFC 3966 §4.1); byte comparison
817                // would call `tel:+1-201-555-0123` and `tel:+12015550123` different numbers.
818                if matches!(self.scheme, Scheme::Tel) {
819                    return tel_equivalent(a, b);
820                }
821                // For schemes sipx does not model, no RFC defines comparison rules, so fall
822                // back to the one thing that cannot be wrong: the bytes, after normalizing
823                // escapes of unreserved characters.
824                return escape::normalize_for_comparison(a) == escape::normalize_for_comparison(b);
825            }
826            _ => return false,
827        };
828
829        // "Comparison of the userinfo ... is case-sensitive", but escapes of unreserved
830        // characters still fold: sip:%61lice@atlanta.com is sip:alice@atlanta.com.
831        if !opt_bytes_equivalent(a.user.as_deref(), b.user.as_deref(), true) {
832            return false;
833        }
834        if !opt_bytes_equivalent(a.password.as_deref(), b.password.as_deref(), true) {
835            return false;
836        }
837        if !a.host.equivalent(&b.host) {
838            return false;
839        }
840        // "A URI omitting any component with a default value will not match a URI explicitly
841        // containing that component with its default value."
842        if a.port != b.port {
843            return false;
844        }
845
846        // "A user, ttl, or method uri-parameter appearing in only one URI never matches",
847        // likewise maddr, and likewise transport per the paragraph above the list.
848        for name in ["user", "ttl", "method", "maddr", "transport"] {
849            if !a.params.param_equivalent(&b.params, name) {
850                return false;
851            }
852        }
853        // "Any uri-parameter appearing in both URIs must match." Others are ignored.
854        if !a.params.common_params_agree(&b.params) || !b.params.common_params_agree(&a.params) {
855            return false;
856        }
857
858        // "URI header components are never ignored. Any present header component MUST be
859        // present in both URIs and match."
860        //
861        // Compared as multisets rather than by looking each one up by name. A URI may carry
862        // the same header name twice with different values — `?f=a&f=b` is legal — and a
863        // lookup returns only the first, so every occurrence after the first would be compared
864        // against the wrong value. That made such a URI unequal to *itself*, which a property
865        // test caught and no example test would have.
866        header_multiset(&a.headers) == header_multiset(&b.headers)
867    }
868}
869
870impl fmt::Display for Uri {
871    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
872        write!(f, "{}", String::from_utf8_lossy(&self.to_bytes()))
873    }
874}
875
876/// The headers of a URI, grouped by name, with each name's values sorted.
877///
878/// Sorted because order carries no meaning for equivalence: `?a=1&b=2` and `?b=2&a=1` are the
879/// same URI. Grouped because a name may repeat.
880fn header_multiset(headers: &Params) -> std::collections::BTreeMap<Vec<u8>, Vec<Vec<u8>>> {
881    let mut grouped: std::collections::BTreeMap<Vec<u8>, Vec<Vec<u8>>> =
882        std::collections::BTreeMap::new();
883    for param in headers.iter() {
884        let name = escape::normalize_for_comparison(param.name()).to_ascii_lowercase();
885        let value = param
886            .value()
887            .map(|value| escape::normalize_for_comparison(value).to_ascii_lowercase())
888            .unwrap_or_default();
889        grouped.entry(name).or_default().push(value);
890    }
891    for values in grouped.values_mut() {
892        values.sort_unstable();
893    }
894    grouped
895}
896
897/// Whether two tel URI bodies — everything after `tel:` — are equivalent under RFC 3966 §4.1.
898///
899/// The number is compared after removing visual separators; both URIs must be global or both
900/// local, which falls out of the comparison because only a global number keeps its leading
901/// `+`. Parameters are compared by name regardless of order, a name present in only one URI
902/// is a difference, and the whole comparison is case-insensitive.
903#[must_use]
904fn tel_equivalent(a: &[u8], b: &[u8]) -> bool {
905    let parts_a = split_tel_body(a);
906    let parts_b = split_tel_body(b);
907
908    if !escape::eq_ignore_ascii_case(
909        &strip_visual_separators(parts_a.subscriber),
910        &strip_visual_separators(parts_b.subscriber),
911    ) {
912        return false;
913    }
914
915    tel_param_multiset(parts_a.parameters.unwrap_or_default())
916        == tel_param_multiset(parts_b.parameters.unwrap_or_default())
917}
918
919/// Split a tel URI body into the telephone-subscriber part and the parameter tail.
920#[must_use]
921fn split_tel_body(body: &[u8]) -> TelUriParts<'_> {
922    match body.iter().position(|&b| b == b';') {
923        Some(semi) => TelUriParts {
924            subscriber: body.get(..semi).unwrap_or(&[]),
925            parameters: Some(body.get(semi + 1..).unwrap_or(&[])),
926        },
927        None => TelUriParts {
928            subscriber: body,
929            parameters: None,
930        },
931    }
932}
933
934fn parse_tel_parameter(
935    segment: &[u8],
936    offset: usize,
937) -> Result<TelParameter<'_>, TelParameterError> {
938    if segment.is_empty() {
939        return Err(TelParameterError {
940            offset,
941            kind: TelParameterErrorKind::Empty,
942        });
943    }
944    let (name, value, value_offset) = match segment.iter().position(|&byte| byte == b'=') {
945        Some(equals) => (
946            segment.get(..equals).unwrap_or(&[]),
947            Some(segment.get(equals.saturating_add(1)..).unwrap_or(&[])),
948            equals.saturating_add(1),
949        ),
950        None => (segment, None, segment.len()),
951    };
952    if name.is_empty() {
953        return Err(TelParameterError {
954            offset,
955            kind: TelParameterErrorKind::Name,
956        });
957    }
958    if let Some(invalid) = name
959        .iter()
960        .position(|&byte| !is_tel_parameter_name_char(byte))
961    {
962        return Err(TelParameterError {
963            offset: offset.checked_add(invalid).unwrap_or(offset),
964            kind: TelParameterErrorKind::Name,
965        });
966    }
967    if let Some(value) = value {
968        if value.is_empty() {
969            return Err(TelParameterError {
970                offset: offset.checked_add(value_offset).unwrap_or(offset),
971                kind: TelParameterErrorKind::Value,
972            });
973        }
974        if let Some(invalid) = invalid_tel_parameter_value_byte(value) {
975            let value_start = offset.checked_add(value_offset).unwrap_or(offset);
976            return Err(TelParameterError {
977                offset: value_start.checked_add(invalid).unwrap_or(value_start),
978                kind: TelParameterErrorKind::Value,
979            });
980        }
981    }
982    Ok(TelParameter { name, value })
983}
984
985#[must_use]
986fn valid_tel_parameter_name(name: &[u8]) -> bool {
987    !name.is_empty() && name.iter().copied().all(is_tel_parameter_name_char)
988}
989
990#[must_use]
991fn is_tel_parameter_name_char(byte: u8) -> bool {
992    byte.is_ascii_alphanumeric() || byte == b'-'
993}
994
995#[must_use]
996fn invalid_tel_parameter_value_byte(value: &[u8]) -> Option<usize> {
997    let mut index = 0;
998    while let Some(&byte) = value.get(index) {
999        if byte == b'%' {
1000            let first = value.get(index.saturating_add(1));
1001            let second = value.get(index.saturating_add(2));
1002            if !first.is_some_and(u8::is_ascii_hexdigit)
1003                || !second.is_some_and(u8::is_ascii_hexdigit)
1004            {
1005                return Some(index);
1006            }
1007            index = index.saturating_add(3);
1008            continue;
1009        }
1010        if !(byte.is_ascii_alphanumeric()
1011            || matches!(
1012                byte,
1013                b'-' | b'.'
1014                    | b'_'
1015                    | b'!'
1016                    | b'~'
1017                    | b'*'
1018                    | b'\''
1019                    | b'('
1020                    | b')'
1021                    | b'['
1022                    | b']'
1023                    | b'/'
1024                    | b':'
1025                    | b'&'
1026                    | b'+'
1027                    | b'$'
1028            ))
1029        {
1030            return Some(index);
1031        }
1032        index = index.saturating_add(1);
1033    }
1034    None
1035}
1036
1037/// Remove the RFC 3966 `visual-separator` characters: `-`, `.`, `(` and `)`.
1038#[must_use]
1039fn strip_visual_separators(number: &[u8]) -> Vec<u8> {
1040    number
1041        .iter()
1042        .copied()
1043        .filter(|b| !matches!(b, b'-' | b'.' | b'(' | b')'))
1044        .collect()
1045}
1046
1047/// The parameters of a tel URI, grouped by name, normalized for the §4.1 comparison.
1048///
1049/// Escapes of unreserved characters fold, and everything lowercases — "URI comparisons are
1050/// case-insensitive". A `phone-context` naming a global number, and an `ext`, are digit
1051/// strings, so their visual separators are removed the same way the number's are.
1052fn tel_param_multiset(params: &[u8]) -> std::collections::BTreeMap<Vec<u8>, Vec<Vec<u8>>> {
1053    let mut grouped: std::collections::BTreeMap<Vec<u8>, Vec<Vec<u8>>> =
1054        std::collections::BTreeMap::new();
1055    if params.is_empty() {
1056        return grouped;
1057    }
1058    for segment in params.split(|&b| b == b';') {
1059        let (name, value) = match segment.iter().position(|&b| b == b'=') {
1060            Some(eq) => (
1061                segment.get(..eq).unwrap_or(&[]),
1062                segment.get(eq + 1..).unwrap_or(&[]),
1063            ),
1064            None => (segment, &[][..]),
1065        };
1066        let name = escape::normalize_for_comparison(name).to_ascii_lowercase();
1067        let mut value = escape::normalize_for_comparison(value).to_ascii_lowercase();
1068        if name == b"ext" || (name == b"phone-context" && value.first() == Some(&b'+')) {
1069            value = strip_visual_separators(&value);
1070        }
1071        grouped.entry(name).or_default().push(value);
1072    }
1073    for values in grouped.values_mut() {
1074        values.sort_unstable();
1075    }
1076    grouped
1077}
1078
1079/// Compare two optional components, folding escapes of unreserved characters. `case_sensitive`
1080/// distinguishes userinfo (case-sensitive) from everything else.
1081#[must_use]
1082fn opt_bytes_equivalent(a: Option<&[u8]>, b: Option<&[u8]>, case_sensitive: bool) -> bool {
1083    match (a, b) {
1084        (None, None) => true,
1085        (Some(x), Some(y)) => {
1086            let (x, y) = (
1087                escape::normalize_for_comparison(x),
1088                escape::normalize_for_comparison(y),
1089            );
1090            if case_sensitive {
1091                x == y
1092            } else {
1093                escape::eq_ignore_ascii_case(&x, &y)
1094            }
1095        }
1096        _ => false,
1097    }
1098}
1099
1100#[must_use]
1101fn is_scheme_char(b: u8) -> bool {
1102    b.is_ascii_alphanumeric() || matches!(b, b'+' | b'-' | b'.')
1103}
1104
1105/// Validate RFC 3261 §25.1's already percent-encoded `user` production.
1106fn validate_user(user: &[u8]) -> Result<(), UriError> {
1107    if !escape::escapes_are_well_formed(user) {
1108        return Err(UriError::PercentEscape);
1109    }
1110    if user.is_empty() || !user.iter().copied().all(is_user_char) {
1111        return Err(UriError::User);
1112    }
1113    Ok(())
1114}
1115
1116/// Validate RFC 3966 §3's `global-number-digits / local-number-digits` production.
1117fn validate_tel_subscriber(subscriber: &[u8]) -> Result<(), UriError> {
1118    let valid = if let Some(rest) = subscriber.strip_prefix(b"+") {
1119        rest.iter().copied().all(is_global_phone_digit) && rest.iter().any(u8::is_ascii_digit)
1120    } else {
1121        subscriber.iter().copied().all(is_local_phone_digit)
1122            && subscriber.iter().copied().any(is_local_phone_symbol)
1123    };
1124    if valid {
1125        Ok(())
1126    } else {
1127        Err(UriError::TelephoneSubscriber)
1128    }
1129}
1130
1131#[must_use]
1132fn is_global_phone_digit(byte: u8) -> bool {
1133    byte.is_ascii_digit() || is_visual_separator(byte)
1134}
1135
1136#[must_use]
1137fn is_local_phone_digit(byte: u8) -> bool {
1138    is_local_phone_symbol(byte) || is_visual_separator(byte)
1139}
1140
1141#[must_use]
1142fn is_local_phone_symbol(byte: u8) -> bool {
1143    byte.is_ascii_hexdigit() || matches!(byte, b'*' | b'#')
1144}
1145
1146#[must_use]
1147fn is_visual_separator(byte: u8) -> bool {
1148    matches!(byte, b'-' | b'.' | b'(' | b')')
1149}
1150
1151#[must_use]
1152fn is_user_char(b: u8) -> bool {
1153    b.is_ascii_alphanumeric()
1154        || matches!(
1155            b,
1156            b'-' | b'_'
1157                | b'.'
1158                | b'!'
1159                | b'~'
1160                | b'*'
1161                | b'\''
1162                | b'('
1163                | b')'
1164                | b'&'
1165                | b'='
1166                | b'+'
1167                | b'$'
1168                | b','
1169                | b';'
1170                | b'?'
1171                | b'/'
1172                | b'%'
1173        )
1174}
1175
1176/// Hostname characters.
1177///
1178/// The ABNF permits only alphanumerics, `-` and `.`. sipx also accepts `_`, which the grammar
1179/// does not: it is common in deployed hostnames, and it is not a delimiter anywhere in the
1180/// URI grammar, so accepting it cannot make a URI ambiguous.
1181#[must_use]
1182fn is_host_char(b: u8) -> bool {
1183    b.is_ascii_alphanumeric() || matches!(b, b'-' | b'.' | b'_')
1184}
1185
1186fn parse_sip_parts(rest: &Bytes, body_offset: usize) -> Result<SipParts, UriError> {
1187    // An unescaped '@' can only be the userinfo separator: it is absent from the character
1188    // sets for user, password, host, parameters and headers alike, so it must be escaped
1189    // anywhere else. That makes the first '@' unambiguous.
1190    let (userinfo, after) = match rest.iter().position(|&b| b == b'@') {
1191        Some(at) => (Some(rest.slice(..at)), rest.slice(at + 1..)),
1192        None => (None, rest.clone()),
1193    };
1194
1195    let (user, password) = match userinfo {
1196        None => (None, None),
1197        Some(info) => match info.iter().position(|&b| b == b':') {
1198            // The password may not contain ':', so the first one separates the two.
1199            Some(c) => (Some(info.slice(..c)), Some(info.slice(c + 1..))),
1200            None => (Some(info), None),
1201        },
1202    };
1203    if let Some(user) = &user {
1204        validate_user(user)?;
1205    }
1206    let raw_user_span = user
1207        .as_ref()
1208        .and_then(|value| body_offset.checked_add(value.len()))
1209        .map(|end| body_offset..end);
1210
1211    for field in [user.as_ref(), password.as_ref()].into_iter().flatten() {
1212        if !escape::escapes_are_well_formed(field) {
1213            return Err(UriError::PercentEscape);
1214        }
1215    }
1216
1217    // Headers start at the first '?'; parameters at the first ';'. Neither character can
1218    // appear unescaped in a host, so scanning left to right is unambiguous here — which is
1219    // only true because userinfo has already been removed. The user part *may* contain both.
1220    let (before_headers, headers_raw) = match after.iter().position(|&b| b == b'?') {
1221        Some(q) => (after.slice(..q), Some(after.slice(q + 1..))),
1222        None => (after.clone(), None),
1223    };
1224    let (hostport, params_raw) = match before_headers.iter().position(|&b| b == b';') {
1225        Some(s) => (
1226            before_headers.slice(..s),
1227            Some(before_headers.slice(s + 1..)),
1228        ),
1229        None => (before_headers.clone(), None),
1230    };
1231
1232    let (host, port) = parse_hostport(&hostport)?;
1233
1234    let params = match params_raw {
1235        Some(raw) => parse_params(&raw, b';')?,
1236        None => Params::new(),
1237    };
1238    let headers = match headers_raw {
1239        Some(raw) => parse_params(&raw, b'&')?,
1240        None => Params::new(),
1241    };
1242
1243    Ok(SipParts {
1244        user,
1245        raw_user_span,
1246        password,
1247        host,
1248        port,
1249        params,
1250        headers,
1251    })
1252}
1253
1254/// Replace a parser-owned span without re-reading URI grammar.
1255fn replace_raw_span(
1256    raw: &Bytes,
1257    span: &std::ops::Range<usize>,
1258    replacement: &[u8],
1259) -> Option<Bytes> {
1260    let before = raw.get(..span.start)?;
1261    let after = raw.get(span.end..)?;
1262    let capacity = before
1263        .len()
1264        .checked_add(replacement.len())?
1265        .checked_add(after.len())?;
1266    let mut rewritten = Vec::with_capacity(capacity);
1267    rewritten.extend_from_slice(before);
1268    rewritten.extend_from_slice(replacement);
1269    rewritten.extend_from_slice(after);
1270    Some(Bytes::from(rewritten))
1271}
1272
1273fn parse_hostport(hostport: &Bytes) -> Result<(Host, Option<u16>), UriError> {
1274    if hostport.is_empty() {
1275        return Err(UriError::EmptyHost);
1276    }
1277
1278    if hostport.first() == Some(&b'[') {
1279        let close = hostport
1280            .iter()
1281            .position(|&b| b == b']')
1282            .ok_or(UriError::Ipv6Reference)?;
1283        let inner = hostport.slice(1..close);
1284        let text = std::str::from_utf8(&inner).map_err(|_| UriError::Host)?;
1285        let ip = parse_ipv6_reference(text)?;
1286        let tail = hostport.slice(close + 1..);
1287        let port = parse_port_suffix(&tail)?;
1288        return Ok((Host::Ip(IpAddr::V6(ip)), port));
1289    }
1290
1291    let (host_raw, port) = match hostport.iter().position(|&b| b == b':') {
1292        Some(c) => (hostport.slice(..c), parse_port(&hostport.slice(c + 1..))?),
1293        None => (hostport.clone(), None),
1294    };
1295
1296    if host_raw.is_empty() {
1297        return Err(UriError::EmptyHost);
1298    }
1299    if !host_raw.iter().all(|&b| is_host_char(b)) {
1300        return Err(UriError::Host);
1301    }
1302
1303    let host = std::str::from_utf8(&host_raw)
1304        .ok()
1305        .and_then(|s| s.parse::<Ipv4Addr>().ok())
1306        .map_or_else(
1307            || Host::Name(HostName::new_unchecked(host_raw.clone())),
1308            |ip| Host::Ip(IpAddr::V4(ip)),
1309        );
1310
1311    Ok((host, port))
1312}
1313
1314/// Parse the text between an IPv6 reference's `[` and `]`.
1315///
1316/// RFC 4291 §2.2 is the address grammar, and `Ipv6Addr`'s own parser implements it. Almost
1317/// everything goes through that parser untouched; this function exists for the one construct
1318/// RFC 4291 forbids and sipx must accept anyway.
1319///
1320/// RFC 3261 §25.1 inherited its `IPv6address` production from the obsoleted RFC 2373:
1321///
1322/// ```abnf
1323/// IPv6address = hexpart [ ":" IPv4address ]
1324/// hexpart     = hexseq / hexseq "::" [ hexseq ] / "::" [ hexseq ]
1325/// ```
1326///
1327/// `hexpart` may end in `"::"`, and the grammar then appends `":" IPv4address` — so RFC 3261's
1328/// own ABNF derives `2001:db8:::192.0.2.1`, with three colons before an embedded IPv4 address.
1329/// RFC 4291 corrected the grammar, but senders had already been written against RFC 3261, and
1330/// RFC 5118 §4.10 is normative about the consequence: "following the Robustness Principle
1331/// [RFC1122], an implementation must tolerate both of the above constructs."
1332///
1333/// # The rule, and why it is this narrow
1334///
1335/// `:::` reads as `::` **only** immediately before an embedded IPv4 address that ends the
1336/// reference — the one position the derivation above can produce it. Everywhere else `:::` stays
1337/// `UriError::Host`. See `docs/specs/sip-parser.md` §4.8.
1338///
1339/// The tolerance is a rewrite of one `:::` into `::` followed by a **retry through the same
1340/// RFC 4291 parser**, never a parser of its own. So the language accepted is exactly RFC 4291
1341/// plus that single derivation: `2001:db8::1:::192.0.2.1` rewrites to a reference with two `::`
1342/// runs and is still rejected, `[2001:db8:::10]` has no embedded IPv4 address and is still
1343/// rejected, and `::::192.0.2.1` leaves a leading colon on the tail and is still rejected.
1344/// Widening the address grammar instead would trade one unmet MUST for an unmeasured surface on
1345/// unauthenticated input.
1346fn parse_ipv6_reference(text: &str) -> Result<Ipv6Addr, UriError> {
1347    if let Ok(ip) = text.parse::<Ipv6Addr>() {
1348        return Ok(ip);
1349    }
1350
1351    // RFC 5118 §4.10. `split_once` takes the *first* `:::`, which is what makes the check below
1352    // sufficient rather than merely indicative: a second `:::`, or a fourth colon, leaves a tail
1353    // that is not an `IPv4address`, and an embedded IPv4 address is by definition the end of the
1354    // reference. So there is no second occurrence to reason about separately.
1355    let (hexpart, embedded) = text.split_once(":::").ok_or(UriError::Host)?;
1356    if embedded.parse::<Ipv4Addr>().is_err() {
1357        return Err(UriError::Host);
1358    }
1359
1360    let mut corrected = String::with_capacity(text.len());
1361    corrected.push_str(hexpart);
1362    corrected.push_str("::");
1363    corrected.push_str(embedded);
1364    corrected.parse::<Ipv6Addr>().map_err(|_| UriError::Host)
1365}
1366
1367fn parse_port_suffix(tail: &Bytes) -> Result<Option<u16>, UriError> {
1368    if tail.is_empty() {
1369        return Ok(None);
1370    }
1371    if tail.first() != Some(&b':') {
1372        return Err(UriError::Host);
1373    }
1374    parse_port(&tail.slice(1..))
1375}
1376
1377fn parse_port(raw: &Bytes) -> Result<Option<u16>, UriError> {
1378    if raw.is_empty() || !raw.iter().all(u8::is_ascii_digit) {
1379        return Err(UriError::Port);
1380    }
1381    // Explicitly bounded: a port is at most five digits, so a long run of digits is rejected
1382    // before any conversion rather than wrapping.
1383    if raw.len() > 5 {
1384        return Err(UriError::Port);
1385    }
1386    let mut value: u32 = 0;
1387    for &b in raw {
1388        value = value * 10 + u32::from(b - b'0');
1389    }
1390    u16::try_from(value).map(Some).map_err(|_| UriError::Port)
1391}
1392
1393fn parse_params(raw: &Bytes, separator: u8) -> Result<Params, UriError> {
1394    let mut params = Params::new();
1395    // A trailing separator with nothing after it (`sip:host;`) is not a parameter list of
1396    // length zero; the ABNF's pname is `1*paramchar`, so there is nothing legal to parse.
1397    if raw.is_empty() {
1398        return Err(UriError::EmptyParameterName);
1399    }
1400    let mut start = 0usize;
1401    let cut = |from: usize, to: usize, params: &mut Params| -> Result<(), UriError> {
1402        let field = raw.slice(from..to);
1403        // Rejected rather than skipped: `;;` is not a quirky spelling of `;`. The header
1404        // grammar takes the same line, and RFC 4475 3.1.2.1 turns a message invalid on
1405        // exactly this in a Via.
1406        if field.is_empty() {
1407            return Err(UriError::EmptyParameterName);
1408        }
1409        let param = match field.iter().position(|&b| b == b'=') {
1410            Some(eq) => {
1411                let name = field.slice(..eq);
1412                if name.is_empty() {
1413                    return Err(UriError::EmptyParameterName);
1414                }
1415                Param::new(name, field.slice(eq + 1..))
1416            }
1417            None => Param::flag(field),
1418        };
1419        if !escape::escapes_are_well_formed(param.name())
1420            || param
1421                .value()
1422                .is_some_and(|v| !escape::escapes_are_well_formed(v))
1423        {
1424            return Err(UriError::PercentEscape);
1425        }
1426        // RFC 3261 §19.1.1: "any given parameter-name MUST NOT appear more than once" among
1427        // uri-parameters. URI headers may legally repeat — `?f=a&f=b` — so only the `;` list
1428        // is policed. Spelling variants count: §19.1.4 makes `%74ransport` the name
1429        // `transport`, so a repeat under an escape or a case change is still a repeat.
1430        if separator == b';'
1431            && params
1432                .iter()
1433                .any(|existing| crate::params::names_equivalent(existing.name(), param.name()))
1434        {
1435            return Err(UriError::DuplicateParameterName);
1436        }
1437        params.push(param);
1438        Ok(())
1439    };
1440
1441    for (i, &b) in raw.iter().enumerate() {
1442        if b == separator {
1443            cut(start, i, &mut params)?;
1444            start = i + 1;
1445        }
1446    }
1447    cut(start, raw.len(), &mut params)?;
1448    Ok(params)
1449}
1450
1451#[cfg(test)]
1452#[allow(
1453    clippy::unwrap_used,
1454    clippy::expect_used,
1455    clippy::panic,
1456    clippy::indexing_slicing
1457)]
1458mod tests {
1459    use super::*;
1460
1461    fn uri(s: &str) -> Uri {
1462        Uri::parse(Bytes::from(s.to_owned())).unwrap_or_else(|e| panic!("{s:?} should parse: {e}"))
1463    }
1464
1465    /// The worked examples from RFC 3261 §19.1.4. Every one of these fails under naive string
1466    /// comparison, which is the entire reason the section exists.
1467    #[test]
1468    fn uri_equivalence_rfc3261_19_1_4() {
1469        let equivalent: &[(&str, &str)] = &[
1470            (
1471                "sip:%61lice@atlanta.com;transport=TCP",
1472                "sip:alice@AtLanTa.CoM;Transport=tcp",
1473            ),
1474            ("sip:carol@chicago.com", "sip:carol@chicago.com;newparam=5"),
1475            ("sip:carol@chicago.com", "sip:carol@chicago.com;security=on"),
1476            (
1477                "sip:carol@chicago.com;newparam=5",
1478                "sip:carol@chicago.com;security=on",
1479            ),
1480            (
1481                "sip:biloxi.com;transport=tcp;method=REGISTER?to=sip:bob%40biloxi.com",
1482                "sip:biloxi.com;method=REGISTER;transport=tcp?to=sip:bob%40biloxi.com",
1483            ),
1484            (
1485                "sip:alice@atlanta.com?subject=project%20x&priority=urgent",
1486                "sip:alice@atlanta.com?priority=urgent&subject=project%20x",
1487            ),
1488        ];
1489        for (a, b) in equivalent {
1490            assert!(
1491                uri(a).equivalent(&uri(b)),
1492                "RFC 3261 19.1.4 says these are equivalent:\n  {a}\n  {b}"
1493            );
1494            assert!(uri(b).equivalent(&uri(a)), "equivalence must be symmetric");
1495        }
1496
1497        let different: &[(&str, &str, &str)] = &[
1498            (
1499                "SIP:ALICE@AtLanTa.CoM;Transport=udp",
1500                "sip:alice@AtLanTa.CoM;Transport=UDP",
1501                "different usernames",
1502            ),
1503            (
1504                "sip:bob@biloxi.com",
1505                "sip:bob@biloxi.com:5060",
1506                "can resolve to different ports",
1507            ),
1508            (
1509                "sip:bob@biloxi.com",
1510                "sip:bob@biloxi.com;transport=udp",
1511                "can resolve to different transports",
1512            ),
1513            (
1514                "sip:bob@biloxi.com",
1515                "sip:bob@biloxi.com:6000;transport=tcp",
1516                "different port and transport",
1517            ),
1518            (
1519                "sip:carol@chicago.com",
1520                "sip:carol@chicago.com?Subject=next%20meeting",
1521                "different header component",
1522            ),
1523            (
1524                "sip:bob@phone21.boxesbybob.com",
1525                "sip:bob@192.0.2.4",
1526                "a hostname never matches an IP address",
1527            ),
1528        ];
1529        for (a, b, why) in different {
1530            assert!(
1531                !uri(a).equivalent(&uri(b)),
1532                "RFC 3261 19.1.4 says these differ ({why}):\n  {a}\n  {b}"
1533            );
1534        }
1535    }
1536
1537    #[test]
1538    fn sip_and_sips_are_never_equivalent() {
1539        assert!(!uri("sip:a@b.com").equivalent(&uri("sips:a@b.com")));
1540    }
1541
1542    /// The RFC 3966 §4.1 rules: visual separators are not part of the number, parameter
1543    /// order and case carry no meaning, and a parameter present in only one URI is a
1544    /// difference.
1545    #[test]
1546    fn tel_uri_equivalence_rfc3966_4_1() {
1547        let equivalent: &[(&str, &str)] = &[
1548            // The §4.1 worked example: separators removed, the numbers are identical.
1549            ("tel:+1-201-555-0123", "tel:+12015550123"),
1550            (
1551                "tel:7042;phone-context=example.com",
1552                "tel:7042;PHONE-CONTEXT=EXAMPLE.COM",
1553            ),
1554            // A global-number phone-context is compared digit by digit, separators removed.
1555            (
1556                "tel:863-1234;phone-context=+1-914-555",
1557                "tel:8631234;phone-context=+1914555",
1558            ),
1559            // Parameter order is insignificant.
1560            (
1561                "tel:7042;ext=1;phone-context=example.com",
1562                "tel:7042;phone-context=example.com;ext=1",
1563            ),
1564        ];
1565        for (a, b) in equivalent {
1566            assert!(
1567                uri(a).equivalent(&uri(b)),
1568                "RFC 3966 4.1 says these are equivalent:\n  {a}\n  {b}"
1569            );
1570            assert!(uri(b).equivalent(&uri(a)), "equivalence must be symmetric");
1571        }
1572
1573        let different: &[(&str, &str, &str)] = &[
1574            (
1575                "tel:+12015550123",
1576                "tel:12015550123",
1577                "a global number never matches a local one",
1578            ),
1579            (
1580                "tel:7042;phone-context=example.com",
1581                "tel:7042",
1582                "a parameter present in only one is a difference",
1583            ),
1584            (
1585                "tel:+1-201-555-0123",
1586                "tel:+1-201-555-0124",
1587                "different numbers",
1588            ),
1589            (
1590                "tel:7042;phone-context=example.com",
1591                "tel:7042;phone-context=example.org",
1592                "different phone-context domains",
1593            ),
1594        ];
1595        for (a, b, why) in different {
1596            assert!(
1597                !uri(a).equivalent(&uri(b)),
1598                "RFC 3966 4.1 says these differ ({why}):\n  {a}\n  {b}"
1599            );
1600        }
1601    }
1602
1603    /// RFC 3261 §19.1.4: characters outside the reserved set are equivalent to their
1604    /// `% HEX HEX` encoding, and `pname` is built from `paramchar`, which includes
1605    /// `escaped` — so `%74ransport` is a legal spelling of `transport`, and the §19.1.4
1606    /// special-parameter rules must see through it.
1607    #[test]
1608    fn escaped_parameter_names_are_the_same_parameter() {
1609        assert!(uri("sip:h;transport=udp").equivalent(&uri("sip:h;%74ransport=udp")));
1610        assert_eq!(uri("sip:h;%74ransport=tcp").transport(), Some(&b"tcp"[..]));
1611
1612        // A one-sided maddr never matches, however its name is spelled.
1613        assert!(!uri("sip:h").equivalent(&uri("sip:h;m%61ddr=239.1.1.1")));
1614        assert!(!uri("sip:h;m%61ddr=239.1.1.1").equivalent(&uri("sip:h")));
1615
1616        // And a non-special parameter present in both must still agree.
1617        assert!(!uri("sip:h;foo=1").equivalent(&uri("sip:h;%66oo=2")));
1618    }
1619
1620    /// The RFC notes this itself: equivalence is not transitive. It is the reason `Uri` does
1621    /// not implement `PartialEq` as equivalence.
1622    #[test]
1623    fn equivalence_is_not_transitive() {
1624        let plain = uri("sip:carol@chicago.com");
1625        let on = uri("sip:carol@chicago.com;security=on");
1626        let off = uri("sip:carol@chicago.com;security=off");
1627        assert!(plain.equivalent(&on));
1628        assert!(plain.equivalent(&off));
1629        assert!(!on.equivalent(&off));
1630    }
1631
1632    #[test]
1633    fn parses_userinfo_with_password() {
1634        let u = uri("sip:alice:secret@atlanta.com");
1635        assert_eq!(u.user(), Some(&b"alice"[..]));
1636        assert_eq!(u.password(), Some(&b"secret"[..]));
1637    }
1638
1639    /// RFC 4475 3.1.1.2: the user part may contain `?`, `;` and `/`, so neither the
1640    /// parameter nor the header scan may run before userinfo has been removed.
1641    #[test]
1642    fn user_part_may_contain_parameter_and_header_delimiters() {
1643        let u = uri(
1644            "sip:1_unusual.URI~(to-be!sure)&isn't+it$/crazy?,/;;*:&it+has=1,weird!*pas$wo~d_too.(doesn't-it)@example.com",
1645        );
1646        assert_eq!(
1647            u.user(),
1648            Some(&b"1_unusual.URI~(to-be!sure)&isn't+it$/crazy?,/;;*"[..])
1649        );
1650        assert_eq!(
1651            u.password(),
1652            Some(&b"&it+has=1,weird!*pas$wo~d_too.(doesn't-it)"[..])
1653        );
1654        assert!(matches!(u.host(), Some(Host::Name(h)) if *h == "example.com"));
1655        assert!(u.params().is_some_and(Params::is_empty));
1656        assert!(!u.has_headers());
1657    }
1658
1659    /// RFC 4475 3.1.1.9: semicolons in the user part are user-part characters, not parameter
1660    /// separators.
1661    #[test]
1662    fn semicolons_in_user_part_are_not_parameters() {
1663        let u = uri("sip:user;par=u%40example.net@example.com");
1664        assert_eq!(u.user(), Some(&b"user;par=u%40example.net"[..]));
1665        assert!(u.params().is_some_and(Params::is_empty));
1666    }
1667
1668    /// RFC 4475 3.1.1.4: the user part is `null-%00-null`. Decoding must yield the NUL, which
1669    /// is why this returns bytes.
1670    #[test]
1671    fn decodes_escaped_null_in_user_part() {
1672        let u = uri("sip:null-%00-null@example.com");
1673        assert_eq!(u.user(), Some(&b"null-%00-null"[..]));
1674        assert_eq!(u.decoded_user(), Some(b"null-\x00-null".to_vec()));
1675        // The escaped form is what goes back on the wire.
1676        assert_eq!(
1677            u.to_bytes(),
1678            Bytes::from_static(b"sip:null-%00-null@example.com")
1679        );
1680    }
1681
1682    #[test]
1683    fn parses_ipv6_reference_with_and_without_port() {
1684        let u = uri("sip:alice@[2001:db8::1]");
1685        assert!(matches!(u.host(), Some(Host::Ip(IpAddr::V6(_)))));
1686        assert_eq!(u.port(), None);
1687
1688        let u = uri("sip:alice@[2001:db8::1]:5061");
1689        assert_eq!(u.port(), Some(5061));
1690    }
1691
1692    /// RFC 5118 §4.10: the three-colon reference RFC 3261's ABNF derives must be tolerated, and
1693    /// must mean the address its two-colon twin means.
1694    #[test]
1695    fn tolerates_three_colons_before_an_embedded_ipv4_address() {
1696        let buggy = uri("sip:user@[2001:db8:::192.0.2.1]");
1697        let correct = uri("sip:user@[2001:db8::192.0.2.1]");
1698        let expected = "2001:db8::192.0.2.1"
1699            .parse::<IpAddr>()
1700            .expect("a valid RFC 4291 address");
1701
1702        for (u, what) in [(&buggy, "three-colon"), (&correct, "two-colon")] {
1703            match u.host() {
1704                Some(Host::Ip(ip)) => assert_eq!(*ip, expected, "{what} form"),
1705                other => panic!("{what} form should be an IPv6 literal, got {other:?}"),
1706            }
1707        }
1708
1709        // Tolerated, not normalised: the reference goes back on the wire as it arrived.
1710        assert_eq!(
1711            buggy.to_bytes(),
1712            Bytes::from_static(b"sip:user@[2001:db8:::192.0.2.1]")
1713        );
1714
1715        // The tolerance reaches a `Via` sent-by too, because both go through `parse_hostport` —
1716        // RFC 3261's ABNF derives the construct wherever `IPv6reference` appears, so a rule that
1717        // held only in the Request-URI would be a second, narrower grammar nobody could cite.
1718        let (host, port) =
1719            Host::parse_hostport(&Bytes::from_static(b"[2001:db8:::192.0.2.1]:5060"))
1720                .expect("a Via sent-by holds an IPv6reference too");
1721        assert!(matches!(host, Host::Ip(ip) if ip == expected));
1722        assert_eq!(port, Some(5060));
1723
1724        // RFC 2373's `hexpart` offers two productions that can end in "::", and both derive the
1725        // three-colon form. §4.10's own message exercises `hexseq "::"`; these are the other one
1726        // (empty `hexseq`) and the same one at full width. Covered here through `Uri::parse` as
1727        // well as in the spec-table pin, so the carve-out is known to work on the R-URI path.
1728        for (input, want) in [
1729            ("sip:user@[:::192.0.2.1]", "::192.0.2.1"),
1730            ("sip:user@[1:2:3:4:5:::192.0.2.1]", "1:2:3:4:5::192.0.2.1"),
1731        ] {
1732            let want = want.parse::<IpAddr>().expect("a valid RFC 4291 address");
1733            match uri(input).host() {
1734                Some(Host::Ip(ip)) => assert_eq!(*ip, want, "{input}"),
1735                other => panic!("{input} should be an IPv6 literal, got {other:?}"),
1736            }
1737        }
1738    }
1739
1740    /// The narrowness is the story: `:::` is read as `::` in exactly one position, and every other
1741    /// place it can appear stays a typed error rather than an address parsed on a guess.
1742    #[test]
1743    fn three_colons_anywhere_but_before_an_embedded_ipv4_address_stay_rejected() {
1744        // The variant is asserted, not merely the failure. `is_err()` alone would have let the
1745        // last two rows pass while the spec named the wrong error for them, and the variant is
1746        // what the transaction layer picks a response code from.
1747        let rejected: &[(&str, UriError)] = &[
1748            // No embedded IPv4 address at all — the derivation cannot produce ':::' here.
1749            ("sip:user@[2001:db8:::10]", UriError::Host),
1750            ("sip:user@[2001:db8:::]", UriError::Host),
1751            ("sip:user@[:::]", UriError::Host),
1752            // ':::' before something that only looks like one.
1753            ("sip:user@[2001:db8:::192.0.2]", UriError::Host),
1754            ("sip:user@[2001:db8:::192.0.2.1.5]", UriError::Host),
1755            ("sip:user@[2001:db8:::192.0.2.256]", UriError::Host),
1756            ("sip:user@[2001:db8:::0192.0.2.1]", UriError::Host),
1757            // A fourth colon is not the derivation; it leaves a colon on the tail.
1758            ("sip:user@[2001:db8::::192.0.2.1]", UriError::Host),
1759            ("sip:user@[::::192.0.2.1]", UriError::Host),
1760            // Two occurrences, and a '::' run already spent — the rewrite must not create a
1761            // second one and have it accepted.
1762            (
1763                "sip:user@[2001:db8:::192.0.2.1:::192.0.2.2]",
1764                UriError::Host,
1765            ),
1766            ("sip:user@[2001:db8::1:::192.0.2.1]", UriError::Host),
1767            // ':::' in the middle rather than before the embedded address.
1768            ("sip:user@[2001:::db8:192.0.2.1]", UriError::Host),
1769            // Unbracketed, and it fails *before* any address parser sees it: the host is split at
1770            // its first ':' and `db8:::192.0.2.1` is rejected as a port. The valid two-colon
1771            // address below fails identically, which is the point — RFC 3261 §19.1.1's brackets
1772            // are what make an IPv6 address reachable at all, not the carve-out.
1773            ("sip:user@2001:db8:::192.0.2.1", UriError::Port),
1774            ("sip:user@2001:db8::192.0.2.1", UriError::Port),
1775        ];
1776        for (input, expected) in rejected {
1777            let got = Uri::parse(Bytes::from((*input).to_owned()));
1778            assert_eq!(
1779                got.as_ref().err(),
1780                Some(expected),
1781                "{input:?} is not RFC 5118 §4.10's construct and must stay {expected:?}, \
1782                 got {got:?}"
1783            );
1784        }
1785    }
1786
1787    #[test]
1788    fn parses_ipv4_literal_as_an_address() {
1789        let u = uri("sip:bob@192.0.2.4");
1790        assert!(matches!(u.host(), Some(Host::Ip(IpAddr::V4(_)))));
1791    }
1792
1793    #[test]
1794    fn scheme_and_parameter_select_one_fail_closed_transport_and_default_port() {
1795        let cases = [
1796            ("sip:h;transport=tcp", UriTransport::Tcp, 5060),
1797            ("sip:h;transport=ws", UriTransport::Ws, 80),
1798            ("sips:h;transport=tcp", UriTransport::Tls, 5061),
1799            ("sips:h;transport=tls", UriTransport::Tls, 5061),
1800            ("sips:h;transport=ws", UriTransport::Wss, 443),
1801            ("sips:h;transport=wss", UriTransport::Wss, 443),
1802        ];
1803        for (input, expected, port) in cases {
1804            let selected = uri(input).selected_transport().expect("supported mapping");
1805            assert_eq!(selected, expected, "{input}");
1806            assert_eq!(selected.default_port(), port, "{input}");
1807        }
1808        assert_eq!(
1809            uri("sips:h;transport=udp").selected_transport(),
1810            Err(UriTransportError::SecureDatagram)
1811        );
1812    }
1813
1814    #[test]
1815    fn unknown_schemes_are_kept_opaque() {
1816        // RFC 4475 3.3.2: a Request-URI with an unknown scheme must parse; answering 416 is
1817        // the application's business.
1818        let u = uri("nobodyKnowsThisScheme:totally-bogus-stuff");
1819        assert!(matches!(u.scheme(), Scheme::Other(_)));
1820        assert_eq!(u.opaque(), Some(&b"totally-bogus-stuff"[..]));
1821        assert!(u.host().is_none());
1822    }
1823
1824    #[test]
1825    fn rejects_malformed_uris() {
1826        let cases: &[(&str, UriError)] = &[
1827            ("sip:alice@example .com", UriError::IllegalCharacter),
1828            ("sip:alice@exa\tmple.com", UriError::IllegalCharacter),
1829            ("<sip:alice@example.com>", UriError::IllegalCharacter),
1830            ("alice@example.com", UriError::Scheme),
1831            (":alice@example.com", UriError::Scheme),
1832            ("sip:", UriError::EmptyHost),
1833            ("sip:alice@", UriError::EmptyHost),
1834            ("sip:alice@host:70000", UriError::Port),
1835            ("sip:alice@host:", UriError::Port),
1836            ("sip:alice@host:12x", UriError::Port),
1837            ("sip:alice@[2001:db8::1", UriError::Ipv6Reference),
1838            ("sip:alice%zz@host", UriError::PercentEscape),
1839        ];
1840        for (input, expected) in cases {
1841            let got = Uri::parse(Bytes::from((*input).to_owned()));
1842            assert_eq!(
1843                got.as_ref().err(),
1844                Some(expected),
1845                "{input:?} should be rejected as {expected:?}, got {got:?}"
1846            );
1847        }
1848    }
1849
1850    #[test]
1851    fn port_five_digits_is_bounded_before_conversion() {
1852        // 99999 is five digits and still out of range; 999999 is rejected on length alone.
1853        assert!(Uri::parse(Bytes::from_static(b"sip:h:99999")).is_err());
1854        assert!(Uri::parse(Bytes::from_static(b"sip:h:999999")).is_err());
1855        assert_eq!(uri("sip:h:65535").port(), Some(65535));
1856    }
1857
1858    #[test]
1859    fn a_parsed_uri_round_trips_byte_exactly() {
1860        for input in [
1861            "sip:vivekg@chair-dnrc.example.com;unknownparam",
1862            "SIP:ALICE@AtLanTa.CoM;Transport=udp",
1863            "sip:biloxi.com;transport=tcp;method=REGISTER?to=sip:bob%40biloxi.com",
1864            "sip:user;par=u%40example.net@example.com",
1865            "sips:alice@[2001:db8::1]:5061;maddr=239.255.255.1;ttl=15",
1866            "nobodyKnowsThisScheme:totally-bogus-stuff",
1867        ] {
1868            assert_eq!(
1869                uri(input).to_bytes(),
1870                Bytes::from(input.to_owned()),
1871                "{input} must survive a round trip unchanged"
1872            );
1873        }
1874    }
1875
1876    #[test]
1877    fn a_constructed_uri_serializes_from_its_parts() {
1878        let mut u = Uri::sip(Host::Name(
1879            HostName::new(Bytes::from_static(b"example.com")).expect("a valid host"),
1880        ));
1881        u.push_param(Param::new(
1882            Bytes::from_static(b"transport"),
1883            Bytes::from_static(b"tcp"),
1884        ));
1885        assert_eq!(
1886            u.to_bytes(),
1887            Bytes::from_static(b"sip:example.com;transport=tcp")
1888        );
1889    }
1890
1891    /// The removal half of the pair. §19.1.1 forbids a repeated `uri-parameter`, so a caller
1892    /// re-setting one of its own must remove it first — and a removal that missed would produce a
1893    /// URI the far end cannot parse at all rather than one that merely says the wrong thing.
1894    #[test]
1895    fn removing_a_uri_parameter_reports_whether_there_was_one() {
1896        let mut u = uri("sip:alice@example.com;transport=tcp;lr");
1897        assert!(
1898            u.remove_param("TRANSPORT"),
1899            "§19.1.4 compares names case-insensitively"
1900        );
1901        assert_eq!(
1902            u.to_bytes(),
1903            Bytes::from_static(b"sip:alice@example.com;lr")
1904        );
1905        assert!(!u.remove_param("transport"), "it was already gone");
1906        assert!(u.remove_param("lr"));
1907        assert_eq!(u.to_bytes(), Bytes::from_static(b"sip:alice@example.com"));
1908        // A scheme sipx does not model has no uri-parameter list, so there is nothing to remove
1909        // and nothing is claimed — the same no-op `push_param` is on one.
1910        let mut opaque = uri("tel:+15551234");
1911        assert!(!opaque.remove_param("transport"));
1912        assert_eq!(opaque.to_bytes(), Bytes::from_static(b"tel:+15551234"));
1913    }
1914
1915    #[test]
1916    fn mutating_a_parsed_uri_drops_its_verbatim_form() {
1917        let mut u = uri("sip:alice@Example.COM");
1918        u.push_param(Param::flag(Bytes::from_static(b"lr")));
1919        // The host keeps its original spelling because the parts hold the original bytes;
1920        // what is lost is only the guarantee of byte-for-byte reproduction.
1921        assert_eq!(
1922            u.to_bytes(),
1923            Bytes::from_static(b"sip:alice@Example.COM;lr")
1924        );
1925    }
1926
1927    /// RFC 3261 §19.1.1: "any given parameter-name MUST NOT appear more than once" among
1928    /// uri-parameters. Accepting a repeat also made equivalence irreflexive, because each
1929    /// occurrence was compared against the other URI's *first* one.
1930    #[test]
1931    fn duplicate_uri_parameter_names_are_rejected() {
1932        for input in [
1933            "sip:h;a=1;a=2",
1934            "sip:h;a=1;a=1",
1935            "sip:h;lr;lr",
1936            // Case and escape spellings of a name are still that name (§19.1.4).
1937            "sip:h;transport=udp;TRANSPORT=tcp",
1938            "sip:h;transport=udp;%74ransport=tcp",
1939        ] {
1940            assert!(
1941                Uri::parse(Bytes::from(input.to_owned())).is_err(),
1942                "{input} should be rejected"
1943            );
1944        }
1945        // URI *headers* may repeat; only uri-parameters are policed.
1946        assert!(Uri::parse(Bytes::from_static(b"sip:a?f=a&f=b")).is_ok());
1947    }
1948
1949    /// `;;` is not a quirky spelling of `;`. The ABNF's pname is `1*paramchar`, and the
1950    /// header grammar takes the same line — RFC 4475 3.1.2.1 makes a message invalid on
1951    /// exactly this, in a `Via`.
1952    #[test]
1953    fn empty_parameter_segments_are_rejected() {
1954        for input in ["sip:host;;a=1", "sip:host;a=1;", "sip:host;", "sip:host;;"] {
1955            assert_eq!(
1956                Uri::parse(Bytes::from(input.to_owned())).err(),
1957                Some(UriError::EmptyParameterName),
1958                "{input} should be rejected"
1959            );
1960        }
1961        // A single well-formed parameter is of course still fine.
1962        assert_eq!(uri("sip:host;a=1").params().map(Params::len), Some(1));
1963    }
1964}