1use std::fmt;
2use std::str::FromStr;
3
4use crate::error::ParseSipUriError;
5use crate::host::Host;
6use crate::params;
7use crate::parse;
8
9type Params = Vec<(String, Option<String>)>;
10type Headers = Vec<(String, String)>;
11
12type UserinfoResult = Result<(Option<String>, Params, Option<String>), ParseSipUriError>;
13type HostportResult =
14 Result<(Host, Option<u16>, Params, Headers, Option<String>), ParseSipUriError>;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
21#[non_exhaustive]
22pub struct SipUri {
23 scheme: Scheme,
24 user: Option<String>,
25 user_params: Vec<(String, Option<String>)>,
26 password: Option<String>,
27 host: Host,
28 port: Option<u16>,
29 params: Vec<(String, Option<String>)>,
30 headers: Vec<(String, String)>,
31 fragment: Option<String>,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36#[non_exhaustive]
37pub enum Scheme {
38 Sip,
40 Sips,
42}
43
44impl fmt::Display for Scheme {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 match self {
47 Scheme::Sip => write!(f, "sip"),
48 Scheme::Sips => write!(f, "sips"),
49 }
50 }
51}
52
53impl SipUri {
54 pub fn new(host: Host) -> Self {
56 SipUri {
57 scheme: Scheme::Sip,
58 user: None,
59 user_params: Vec::new(),
60 password: None,
61 host,
62 port: None,
63 params: Vec::new(),
64 headers: Vec::new(),
65 fragment: None,
66 }
67 }
68
69 pub fn with_scheme(mut self, scheme: Scheme) -> Self {
71 self.scheme = scheme;
72 self
73 }
74
75 pub fn with_user(mut self, user: impl Into<String>) -> Self {
77 self.user = Some(user.into());
78 self
79 }
80
81 pub fn with_user_params(mut self, params: Vec<(String, Option<String>)>) -> Self {
83 self.user_params = params;
84 self
85 }
86
87 pub fn with_user_param(mut self, name: impl Into<String>, value: Option<String>) -> Self {
89 self.user_params
90 .push((name.into(), value));
91 self
92 }
93
94 pub fn with_password(mut self, password: impl Into<String>) -> Self {
96 self.password = Some(password.into());
97 self
98 }
99
100 pub fn with_port(mut self, port: u16) -> Self {
102 self.port = Some(port);
103 self
104 }
105
106 pub fn with_param(mut self, name: impl Into<String>, value: Option<String>) -> Self {
108 self.params
109 .push((name.into(), value));
110 self
111 }
112
113 pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
115 self.headers
116 .push((name.into(), value.into()));
117 self
118 }
119
120 pub fn scheme(&self) -> Scheme {
122 self.scheme
123 }
124
125 pub fn user(&self) -> Option<&str> {
127 self.user
128 .as_deref()
129 }
130
131 pub fn user_params(&self) -> &[(String, Option<String>)] {
135 &self.user_params
136 }
137
138 pub fn password(&self) -> Option<&str> {
140 self.password
141 .as_deref()
142 }
143
144 pub fn host(&self) -> &Host {
146 &self.host
147 }
148
149 pub fn port(&self) -> Option<u16> {
151 self.port
152 }
153
154 pub fn params(&self) -> &[(String, Option<String>)] {
156 &self.params
157 }
158
159 pub fn param(&self, name: &str) -> Option<&Option<String>> {
161 params::find_param(&self.params, name)
162 }
163
164 pub fn headers(&self) -> &[(String, String)] {
166 &self.headers
167 }
168
169 pub fn header(&self, name: &str) -> Option<&str> {
171 self.headers
172 .iter()
173 .find(|(n, _)| n.eq_ignore_ascii_case(name))
174 .map(|(_, v)| v.as_str())
175 }
176
177 pub fn fragment(&self) -> Option<&str> {
182 self.fragment
183 .as_deref()
184 }
185
186 pub fn with_fragment(mut self, fragment: impl Into<String>) -> Self {
188 self.fragment = Some(fragment.into());
189 self
190 }
191
192 pub fn user_host(&self) -> String {
194 let mut s = String::new();
195 if let Some(ref u) = self.user {
196 s.push_str(u);
197 s.push('@');
198 }
199 s.push_str(
200 &self
201 .host
202 .to_string(),
203 );
204 if let Some(p) = self.port {
205 s.push(':');
206 s.push_str(&p.to_string());
207 }
208 s
209 }
210}
211
212impl FromStr for SipUri {
213 type Err = ParseSipUriError;
214
215 fn from_str(input: &str) -> Result<Self, Self::Err> {
216 let err = |msg: &str| ParseSipUriError(msg.to_string());
217
218 let colon_pos = input
220 .find(':')
221 .ok_or_else(|| err("missing scheme"))?;
222 let scheme_str = &input[..colon_pos];
223 let scheme = if scheme_str.eq_ignore_ascii_case("sip") {
224 Scheme::Sip
225 } else if scheme_str.eq_ignore_ascii_case("sips") {
226 Scheme::Sips
227 } else {
228 return Err(err(&format!("unknown scheme '{scheme_str}'")));
229 };
230
231 let rest = &input[colon_pos + 1..];
232
233 let (userinfo, hostport_rest) = split_userinfo_host(rest)?;
237
238 let (user, user_params, password) = if let Some(uinfo) = userinfo {
240 parse_userinfo(uinfo)?
241 } else {
242 (None, Vec::new(), None)
243 };
244
245 let (host, port, uri_params, headers, fragment) =
247 parse_hostport_params_headers(hostport_rest)?;
248
249 Ok(SipUri {
250 scheme,
251 user,
252 user_params,
253 password,
254 host,
255 port,
256 params: uri_params,
257 headers,
258 fragment,
259 })
260 }
261}
262
263fn split_userinfo_host(s: &str) -> Result<(Option<&str>, &str), ParseSipUriError> {
268 let err = |msg: &str| ParseSipUriError(msg.to_string());
269
270 if let Some(at_pos) = parse::find_userinfo_at(s) {
279 if at_pos == 0 {
280 return Err(err("empty userinfo before @"));
281 }
282 let userinfo = &s[..at_pos];
283 let rest = &s[at_pos + 1..];
284 if rest.is_empty() {
285 return Err(err("missing host after @"));
286 }
287 Ok((Some(userinfo), rest))
288 } else {
289 Ok((None, s))
291 }
292}
293
294fn parse_userinfo(s: &str) -> UserinfoResult {
305 let err = |msg: &str| ParseSipUriError(msg.to_string());
306
307 let (user_and_params, password) = if let Some(colon_pos) = s.find(':') {
315 let pwd = &s[colon_pos + 1..];
316 (&s[..colon_pos], Some(parse::canonize_password(pwd)))
317 } else {
318 (s, None)
319 };
320
321 if let Some(semi_pos) = user_and_params.find(';') {
325 let user_part = &user_and_params[..semi_pos];
326 let params_str = &user_and_params[semi_pos + 1..];
327
328 if user_part.is_empty() {
329 return Err(err("empty user before ';'"));
330 }
331
332 let user = parse::canonize_user(user_part);
333 let user_params =
334 params::parse_user_params(params_str).map_err(|e| err(&format!("user param: {e}")))?;
335
336 Ok((Some(user), user_params, password))
337 } else if user_and_params.is_empty() {
338 Ok((None, Vec::new(), password))
339 } else {
340 let user = parse::canonize_user(user_and_params);
341 Ok((Some(user), Vec::new(), password))
342 }
343}
344
345fn parse_hostport_params_headers(s: &str) -> HostportResult {
347 let err = |msg: &str| ParseSipUriError(msg.to_string());
348
349 let (host, consumed) = Host::parse_from_uri(s).map_err(|e| err(&e))?;
351
352 let rest = &s[consumed..];
353
354 let (port, rest) = if let Some(rest) = rest.strip_prefix(':') {
356 let end = rest
358 .find([';', '?', '#', '>'])
359 .unwrap_or(rest.len());
360 let port_str = &rest[..end];
361
362 if port_str.is_empty() {
363 (None, &rest[end..])
365 } else {
366 let port: u16 = port_str
367 .parse()
368 .map_err(|_| err(&format!("invalid port '{port_str}'")))?;
369 (Some(port), &rest[end..])
370 }
371 } else {
372 (None, rest)
373 };
374
375 let (rest, fragment) = if let Some(hash_pos) = rest.find('#') {
377 let frag = &rest[hash_pos + 1..];
378 let frag = if frag.is_empty() {
379 None
380 } else {
381 Some(frag.to_string())
382 };
383 (&rest[..hash_pos], frag)
384 } else {
385 (rest, None)
386 };
387
388 let (params_str, headers_str) = if let Some(rest) = rest.strip_prefix(';') {
390 if let Some(q_pos) = rest.find('?') {
392 (&rest[..q_pos], Some(&rest[q_pos + 1..]))
393 } else {
394 (rest, None)
395 }
396 } else if let Some(rest) = rest.strip_prefix('?') {
397 ("", Some(rest))
398 } else if rest.is_empty() {
399 ("", None)
400 } else {
401 return Err(err(&format!(
402 "unexpected character after host/port: '{rest}'"
403 )));
404 };
405
406 let uri_params =
407 params::parse_params(params_str).map_err(|e| err(&format!("URI param: {e}")))?;
408
409 let headers = if let Some(h) = headers_str {
410 params::parse_headers(h).map_err(|e| err(&format!("header: {e}")))?
411 } else {
412 Vec::new()
413 };
414
415 Ok((host, port, uri_params, headers, fragment))
416}
417
418impl fmt::Display for SipUri {
419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420 write!(f, "{}:", self.scheme)?;
421
422 if let Some(ref user) = self.user {
424 write!(f, "{user}")?;
425
426 params::format_params(&self.user_params, f)?;
428
429 if let Some(ref pwd) = self.password {
431 write!(f, ":{pwd}")?;
432 }
433
434 write!(f, "@")?;
435 }
436
437 self.host
439 .fmt_uri(f)?;
440
441 if let Some(port) = self.port {
443 write!(f, ":{port}")?;
444 }
445
446 params::format_params(&self.params, f)?;
448
449 params::format_headers(&self.headers, f)?;
451
452 if let Some(ref frag) = self.fragment {
454 write!(f, "#{frag}")?;
455 }
456
457 Ok(())
458 }
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464 use std::net::Ipv4Addr;
465
466 #[test]
467 fn parse_simple() {
468 let uri: SipUri = "sip:joe@example.com"
469 .parse()
470 .unwrap();
471 assert_eq!(uri.scheme(), Scheme::Sip);
472 assert_eq!(uri.user(), Some("joe"));
473 assert_eq!(uri.host(), &Host::Hostname("example.com".into()));
474 assert_eq!(uri.port(), None);
475 }
476
477 #[test]
478 fn parse_minimal_user_host() {
479 let uri: SipUri = "sip:u@h"
480 .parse()
481 .unwrap();
482 assert_eq!(uri.user(), Some("u"));
483 assert_eq!(uri.host(), &Host::Hostname("h".into()));
484 }
485
486 #[test]
487 fn parse_host_only() {
488 let uri: SipUri = "sip:test.host"
489 .parse()
490 .unwrap();
491 assert_eq!(uri.user(), None);
492 assert_eq!(uri.host(), &Host::Hostname("test.host".into()));
493 }
494
495 #[test]
496 fn parse_ipv4_host() {
497 let uri: SipUri = "sip:172.21.55.55"
498 .parse()
499 .unwrap();
500 assert_eq!(uri.host(), &Host::IPv4(Ipv4Addr::new(172, 21, 55, 55)));
501 }
502
503 #[test]
504 fn parse_ipv4_with_port() {
505 let uri: SipUri = "sip:172.21.55.55:5060"
506 .parse()
507 .unwrap();
508 assert_eq!(uri.host(), &Host::IPv4(Ipv4Addr::new(172, 21, 55, 55)));
509 assert_eq!(uri.port(), Some(5060));
510 }
511
512 #[test]
513 fn parse_full_sips() {
514 let uri: SipUri = "sips:user:pass@host:32;param=1?From=foo@bar&To=bar@baz"
515 .parse()
516 .unwrap();
517 assert_eq!(uri.scheme(), Scheme::Sips);
518 assert_eq!(uri.user(), Some("user"));
519 assert_eq!(uri.password(), Some("pass"));
520 assert_eq!(uri.host(), &Host::Hostname("host".into()));
521 assert_eq!(uri.port(), Some(32));
522 assert_eq!(uri.params(), &[("param".into(), Some("1".into()))]);
523 assert_eq!(uri.header("From"), Some("foo@bar"));
524 assert_eq!(uri.header("To"), Some("bar@baz"));
525 }
526
527 #[test]
528 fn parse_case_insensitive_scheme() {
529 let uri: SipUri = "SIP:test@127.0.0.1:55"
530 .parse()
531 .unwrap();
532 assert_eq!(uri.scheme(), Scheme::Sip);
533 assert_eq!(uri.user(), Some("test"));
534 assert_eq!(uri.port(), Some(55));
535 }
536
537 #[test]
538 fn parse_empty_port() {
539 let uri: SipUri = "SIP:test@127.0.0.1:"
540 .parse()
541 .unwrap();
542 assert_eq!(uri.scheme(), Scheme::Sip);
543 assert_eq!(uri.port(), None);
544 }
545
546 #[test]
547 fn parse_percent_encoded_user() {
548 let uri: SipUri = "sip:%22foo%22@172.21.55.55:5060"
549 .parse()
550 .unwrap();
551 assert_eq!(uri.user(), Some("%22foo%22"));
553 }
554
555 #[test]
556 fn parse_user_with_slash_semicolon() {
557 let uri: SipUri = "sip:user/path;tel-param:pass@host:32;param=1%3d%3d1"
558 .parse()
559 .unwrap();
560 assert_eq!(uri.user(), Some("user/path"));
561 assert_eq!(uri.user_params(), &[("tel-param".into(), None)]);
562 assert_eq!(uri.password(), Some("pass"));
563 assert_eq!(uri.params(), &[("param".into(), Some("1%3D%3D1".into()))]);
565 }
566
567 #[test]
568 fn parse_reserved_chars_in_user_ipv6() {
569 let uri: SipUri = "sip:&=+$,;?/:&=+$,@[::1]:56001;param=+$,/:@&"
570 .parse()
571 .unwrap();
572 assert_eq!(uri.user(), Some("&=+$,"));
573 assert_eq!(uri.user_params(), &[("?/".into(), None)]);
576 assert_eq!(uri.password(), Some("&=+$,"));
577 assert_eq!(
578 uri.host(),
579 &Host::IPv6(
580 "::1"
581 .parse()
582 .unwrap()
583 )
584 );
585 assert_eq!(uri.port(), Some(56001));
586 }
587
588 #[test]
589 fn parse_hash_in_user() {
590 let uri: SipUri = "SIP:#**00**#;foo=/bar@127.0.0.1"
592 .parse()
593 .unwrap();
594 assert_eq!(uri.user(), Some("#**00**#"));
595 assert_eq!(uri.user_params(), &[("foo".into(), Some("/bar".into()))]);
596 }
597
598 #[test]
599 fn parse_transport_params() {
600 let uri: SipUri = "sip:u:p@host:5060;maddr=127.0.0.1;transport=tcp"
601 .parse()
602 .unwrap();
603 assert_eq!(uri.param("transport"), Some(&Some("tcp".into())));
604 assert_eq!(uri.param("maddr"), Some(&Some("127.0.0.1".into())));
605 }
606
607 #[test]
608 fn parse_params_without_value() {
609 let uri: SipUri = "sip:u:p@host:5060;user=phone;ttl=1;isfocus"
610 .parse()
611 .unwrap();
612 assert_eq!(uri.param("user"), Some(&Some("phone".into())));
613 assert_eq!(uri.param("isfocus"), Some(&None));
614 }
615
616 #[test]
617 fn invalid_double_colon_port() {
618 assert!("sip:test@127.0.0.1::55"
619 .parse::<SipUri>()
620 .is_err());
621 }
622
623 #[test]
624 fn invalid_trailing_colon_port() {
625 assert!("sip:test@127.0.0.1:55:"
626 .parse::<SipUri>()
627 .is_err());
628 }
629
630 #[test]
631 fn invalid_non_numeric_port() {
632 assert!("sip:test@127.0.0.1:sip"
633 .parse::<SipUri>()
634 .is_err());
635 }
636
637 #[test]
638 fn display_roundtrip_simple() {
639 let input = "sip:joe@example.com";
640 let uri: SipUri = input
641 .parse()
642 .unwrap();
643 assert_eq!(uri.to_string(), input);
644 }
645
646 #[test]
647 fn display_roundtrip_full() {
648 let uri: SipUri = "sips:user:pass@host:32;param=1?From=foo@bar&To=bar@baz"
649 .parse()
650 .unwrap();
651 assert_eq!(
652 uri.to_string(),
653 "sips:user:pass@host:32;param=1?From=foo@bar&To=bar@baz"
654 );
655 }
656
657 #[test]
658 fn builder() {
659 let uri = SipUri::new(Host::Hostname("example.com".into()))
660 .with_user("alice")
661 .with_param("transport", Some("tcp".into()));
662 assert_eq!(uri.to_string(), "sip:alice@example.com;transport=tcp");
663 }
664
665 #[test]
666 fn user_host_convenience() {
667 let uri: SipUri = "sip:alice@example.com:5060"
668 .parse()
669 .unwrap();
670 assert_eq!(uri.user_host(), "alice@example.com:5060");
671 }
672
673 #[test]
674 fn no_user_with_host_params() {
675 let uri: SipUri = "sip:172.21.55.55:5060;transport=udp"
676 .parse()
677 .unwrap();
678 assert_eq!(uri.user(), None);
679 assert_eq!(uri.port(), Some(5060));
680 assert_eq!(uri.param("transport"), Some(&Some("udp".into())));
681 }
682}