rvoip_sip_core/types/uri.rs
1//! # SIP URI Implementation
2//!
3//! This module provides a comprehensive implementation of SIP Uniform Resource Identifiers (URIs)
4//! as defined in [RFC 3261](https://tools.ietf.org/html/rfc3261).
5//!
6//! SIP URIs are used to identify users, servers, and services in a SIP network.
7//! They have a similar structure to email addresses, with additional parameters
8//! and headers for SIP-specific functionality.
9//!
10//! ## URI Structure
11//!
12//! A SIP URI has the following general form:
13//!
14//! ```text
15//! sip:user:password@host:port;uri-parameters?headers
16//! ```
17//!
18//! Where:
19//! - `sip:` is the scheme (can also be `sips:` for secure SIP)
20//! - `user:password` is the optional userinfo component (password is deprecated)
21//! - `host` is the required domain name or IP address
22//! - `port` is the optional port number
23//! - `uri-parameters` are optional parameters (`;key=value` or `;key`)
24//! - `headers` are optional headers (`?key=value&key=value`)
25//!
26//! ## Usage Examples
27//!
28//! ```rust
29//! use rvoip_sip_core::prelude::*;
30//! use std::str::FromStr;
31//!
32//! // Parse a URI from a string
33//! let uri = Uri::from_str("sip:alice@example.com:5060;transport=udp?subject=meeting").unwrap();
34//!
35//! // Access URI components
36//! assert_eq!(uri.scheme, Scheme::Sip);
37//! assert_eq!(uri.username(), Some("alice"));
38//! assert_eq!(uri.host.to_string(), "example.com");
39//! assert_eq!(uri.port, Some(5060));
40//! assert_eq!(uri.transport(), Some("udp"));
41//!
42//! // Create a URI programmatically
43//! let uri = Uri::sip("example.com")
44//! .with_user("bob")
45//! .with_port(5060)
46//! .with_parameter(Param::transport("tcp"));
47//!
48//! assert_eq!(uri.to_string(), "sip:bob@example.com:5060;transport=tcp");
49//! ```
50
51// URI implementation moved from root directory
52// Implements URI types according to RFC 3261
53
54use std::collections::HashMap;
55use std::fmt;
56use std::str::FromStr;
57use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
58
59use nom::{
60 branch::alt,
61 bytes::complete::{tag, take_till, take_until, take_while, take_while1},
62 character::complete::{char, digit1, hex_digit1},
63 combinator::{map, map_res, opt, recognize, verify, all_consuming},
64 multi::{many0, separated_list0},
65 sequence::{delimited, pair, preceded, separated_pair, terminated, tuple},
66 IResult,
67};
68use serde::{Deserialize, Serialize};
69
70use crate::error::{Error, Result};
71use crate::types::param::Param; // Updated import path
72use crate::parser::uri::parse_uri; // Import the nom parser
73
74/// SIP URI scheme types
75///
76/// Represents the scheme component of a URI, which indicates the protocol
77/// or addressing scheme being used.
78///
79/// The most common schemes in SIP are:
80/// - `sip`: Standard SIP (typically over UDP or TCP)
81/// - `sips`: Secure SIP (typically over TLS)
82/// - `tel`: Telephone number
83///
84/// The implementation also supports `http`, `https`, and custom schemes.
85#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
86pub enum Scheme {
87 /// SIP URI (non-secure)
88 Sip,
89 /// SIPS URI (secure SIP)
90 Sips,
91 /// TEL URI (telephone number)
92 Tel,
93 /// HTTP URI
94 Http,
95 /// HTTPS URI
96 Https,
97 /// Custom scheme (any other scheme)
98 Custom(String),
99}
100
101impl Scheme {
102 /// Returns the string representation of the scheme
103 ///
104 /// # Examples
105 ///
106 /// ```
107 /// use rvoip_sip_core::types::uri::Scheme;
108 ///
109 /// assert_eq!(Scheme::Sip.as_str(), "sip");
110 /// assert_eq!(Scheme::Sips.as_str(), "sips");
111 /// assert_eq!(Scheme::Custom("xmpp".to_string()).as_str(), "xmpp");
112 /// ```
113 pub fn as_str(&self) -> &str {
114 match self {
115 Scheme::Sip => "sip",
116 Scheme::Sips => "sips",
117 Scheme::Tel => "tel",
118 Scheme::Http => "http",
119 Scheme::Https => "https",
120 Scheme::Custom(scheme) => scheme,
121 }
122 }
123}
124
125impl fmt::Display for Scheme {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 f.write_str(self.as_str())
128 }
129}
130
131impl FromStr for Scheme {
132 type Err = Error;
133
134 fn from_str(s: &str) -> Result<Self> {
135 // Check specifically for schemes followed by ':' implicitly
136 // The nom parser `terminated(scheme_parser, char(':'))` ensures this,
137 // so direct FromStr should handle the base scheme string correctly.
138 match s.to_lowercase().as_str() {
139 "sip" => Ok(Scheme::Sip),
140 "sips" => Ok(Scheme::Sips),
141 "tel" => Ok(Scheme::Tel),
142 "http" => Ok(Scheme::Http),
143 "https" => Ok(Scheme::Https),
144 _ => Ok(Scheme::Custom(s.to_string())), // Support arbitrary schemes
145 }
146 }
147}
148
149/// Represents the host part of a URI.
150///
151/// The host can be either a domain name or an IP address (v4 or v6).
152/// In SIP URIs, the host is the mandatory component that identifies the target
153/// server or endpoint.
154///
155/// # Examples
156///
157/// ```
158/// use rvoip_sip_core::prelude::*;
159/// use std::str::FromStr;
160///
161/// // Domain name
162/// let host = Host::domain("example.com");
163/// assert_eq!(host.to_string(), "example.com");
164///
165/// // IPv4 address
166/// let host = Host::from_str("192.168.1.1").unwrap();
167/// assert!(matches!(host, Host::Address(_)));
168///
169/// // IPv6 address
170/// let host = Host::from_str("[2001:db8::1]").unwrap();
171/// assert!(matches!(host, Host::Address(_)));
172/// ```
173#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
174pub enum Host {
175 /// A domain name (e.g., "example.com").
176 Domain(String),
177 /// An IP address (v4 or v6).
178 Address(IpAddr),
179}
180
181impl Host {
182 /// Create a new host from a domain name
183 ///
184 /// # Parameters
185 /// - `domain`: The domain name string
186 ///
187 /// # Returns
188 /// A new Host instance with the Domain variant
189 pub fn domain(domain: impl Into<String>) -> Self {
190 Host::Domain(domain.into())
191 }
192
193 /// Create a new host from an IPv4 address
194 ///
195 /// # Parameters
196 /// - `ip`: The IPv4 address as a string
197 ///
198 /// # Returns
199 /// A new Host instance with the Address variant
200 ///
201 /// # Panics
202 /// Panics if the input string is not a valid IPv4 address
203 pub fn ipv4(ip: impl Into<String>) -> Self {
204 Host::Address(IpAddr::V4(Ipv4Addr::from_str(ip.into().as_str()).unwrap()))
205 }
206
207 /// Create a new host from an IPv6 address
208 ///
209 /// # Parameters
210 /// - `ip`: The IPv6 address as a string
211 ///
212 /// # Returns
213 /// A new Host instance with the Address variant
214 ///
215 /// # Panics
216 /// Panics if the input string is not a valid IPv6 address
217 pub fn ipv6(ip: impl Into<String>) -> Self {
218 Host::Address(IpAddr::V6(Ipv6Addr::from_str(ip.into().as_str()).unwrap()))
219 }
220
221 /// Get the host as a string slice (only works for domain names).
222 /// For addresses, it converts to String.
223 ///
224 /// # Returns
225 /// The host as a string
226 pub fn as_str(&self) -> String {
227 match self {
228 Host::Domain(domain) => domain.clone(),
229 Host::Address(addr) => addr.to_string(),
230 }
231 }
232}
233
234impl fmt::Display for Host {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 match self {
237 Host::Domain(domain) => write!(f, "{}", domain),
238 Host::Address(addr) => write!(f, "{}", addr),
239 }
240 }
241}
242
243impl FromStr for Host {
244 type Err = Error;
245
246 fn from_str(s: &str) -> Result<Self> {
247 // Attempt to parse as IP address first
248 if let Ok(addr) = IpAddr::from_str(s) {
249 return Ok(Host::Address(addr));
250 } else if s.starts_with('[') {
251 if s.ends_with(']') {
252 // Properly formatted IPv6 with brackets
253 if let Ok(addr) = Ipv6Addr::from_str(&s[1..s.len()-1]) {
254 return Ok(Host::Address(IpAddr::V6(addr)));
255 }
256 // If it looked like IPv6 but failed, treat as domain
257 Ok(Host::Domain(s.to_string()))
258 } else {
259 // String starts with '[' but doesn't end with ']' - malformed IPv6
260 Err(Error::InvalidUri(format!("Malformed IPv6 address (unclosed bracket): {}", s)))
261 }
262 } else {
263 // Assume domain name if not a valid IP
264 // TODO: Add stricter domain name validation?
265 if s.is_empty() {
266 Err(Error::ParseError("Host cannot be empty".to_string()))
267 } else {
268 Ok(Host::Domain(s.to_string()))
269 }
270 }
271 }
272}
273
274impl From<IpAddr> for Host {
275 fn from(addr: IpAddr) -> Self {
276 Host::Address(addr)
277 }
278}
279
280impl From<Ipv4Addr> for Host {
281 fn from(addr: Ipv4Addr) -> Self {
282 Host::Address(IpAddr::V4(addr))
283 }
284}
285
286impl From<Ipv6Addr> for Host {
287 fn from(addr: Ipv6Addr) -> Self {
288 Host::Address(IpAddr::V6(addr))
289 }
290}
291
292/// SIP URI components as defined in RFC 3261
293///
294/// Represents a complete SIP URI with all its components. URIs are used throughout
295/// the SIP protocol to identify endpoints, proxy servers, redirect servers, and
296/// other network elements.
297///
298/// # Structure
299///
300/// A complete SIP URI has the following format:
301/// `sip:user:password@host:port;uri-parameters?headers`
302///
303/// # Examples
304///
305/// ```rust
306/// use rvoip_sip_core::prelude::*;
307/// use std::str::FromStr;
308///
309/// // Parse a URI from a string
310/// let uri = Uri::from_str("sip:alice@example.com").unwrap();
311///
312/// // Create a URI programmatically
313/// let uri = Uri::sip("example.com")
314/// .with_user("bob")
315/// .with_port(5060)
316/// .with_parameter(Param::transport("tcp"));
317///
318/// // Get components
319/// assert_eq!(uri.scheme.as_str(), "sip");
320/// assert_eq!(uri.username(), Some("bob"));
321/// assert_eq!(uri.port, Some(5060));
322/// assert_eq!(uri.transport(), Some("tcp"));
323/// ```
324#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
325pub struct Uri {
326 /// URI scheme (sip, sips, tel)
327 pub scheme: Scheme,
328 /// User part (optional)
329 pub user: Option<String>,
330 /// Password (optional, deprecated)
331 pub password: Option<String>,
332 /// Host (required)
333 pub host: Host,
334 /// Port (optional)
335 pub port: Option<u16>,
336 /// URI parameters (;key=value or ;key)
337 pub parameters: Vec<Param>,
338 /// URI headers (?key=value)
339 pub headers: HashMap<String, String>,
340 /// Raw URI string for custom schemes
341 pub raw_uri: Option<String>,
342}
343
344impl Uri {
345 /// Create a new URI with the minimum required fields
346 ///
347 /// # Parameters
348 /// - `scheme`: The URI scheme
349 /// - `host`: The host part
350 ///
351 /// # Returns
352 /// A new URI instance with the given scheme and host
353 pub fn new(scheme: Scheme, host: Host) -> Self {
354 Uri {
355 scheme,
356 user: None,
357 password: None,
358 host,
359 port: None,
360 parameters: Vec::new(),
361 headers: HashMap::new(),
362 raw_uri: None,
363 }
364 }
365
366 /// Returns the scheme of this URI
367 ///
368 /// # Returns
369 /// The scheme (e.g., Sip, Sips, Tel)
370 pub fn scheme(&self) -> &Scheme {
371 &self.scheme
372 }
373
374 /// Returns the host and port (if present) formatted as a string
375 ///
376 /// # Returns
377 /// The host and port as a string (e.g., "example.com:5060")
378 pub fn host_port(&self) -> String {
379 match self.port {
380 Some(port) if port > 0 => format!("{}:{}", self.host, port),
381 _ => self.host.to_string()
382 }
383 }
384
385 /// Create a new SIP URI with a domain host
386 ///
387 /// # Parameters
388 /// - `host`: The domain name
389 ///
390 /// # Returns
391 /// A new URI with SIP scheme and the given domain host
392 ///
393 /// # Examples
394 ///
395 /// ```
396 /// use rvoip_sip_core::prelude::*;
397 ///
398 /// let uri = Uri::sip("example.com");
399 /// assert_eq!(uri.to_string(), "sip:example.com");
400 /// ```
401 pub fn sip(host: impl Into<String>) -> Self {
402 Self::new(Scheme::Sip, Host::domain(host))
403 }
404
405 /// Create a new SIP URI with an IPv4 host
406 ///
407 /// # Parameters
408 /// - `host`: The IPv4 address as a string
409 ///
410 /// # Returns
411 /// A new URI with SIP scheme and the given IPv4 host
412 ///
413 /// # Examples
414 ///
415 /// ```
416 /// use rvoip_sip_core::prelude::*;
417 ///
418 /// let uri = Uri::sip_ipv4("192.168.1.1");
419 /// assert_eq!(uri.to_string(), "sip:192.168.1.1");
420 /// ```
421 pub fn sip_ipv4(host: impl Into<String>) -> Self {
422 Self::new(Scheme::Sip, Host::ipv4(host))
423 }
424
425 /// Create a new SIP URI with an IPv6 host
426 ///
427 /// # Parameters
428 /// - `host`: The IPv6 address as a string
429 ///
430 /// # Returns
431 /// A new URI with SIP scheme and the given IPv6 host
432 ///
433 /// # Examples
434 ///
435 /// ```
436 /// use rvoip_sip_core::prelude::*;
437 ///
438 /// let uri = Uri::sip_ipv6("2001:db8::1");
439 /// assert_eq!(uri.to_string(), "sip:[2001:db8::1]");
440 /// ```
441 pub fn sip_ipv6(host: impl Into<String>) -> Self {
442 Self::new(Scheme::Sip, Host::ipv6(host))
443 }
444
445 /// Create a new SIPS URI
446 ///
447 /// # Parameters
448 /// - `host`: The domain name
449 ///
450 /// # Returns
451 /// A new URI with SIPS scheme and the given domain host
452 pub fn sips(host: impl Into<String>) -> Self {
453 Self::new(Scheme::Sips, Host::domain(host))
454 }
455
456 /// Create a new TEL URI
457 ///
458 /// # Parameters
459 /// - `number`: The telephone number
460 ///
461 /// # Returns
462 /// A new URI with TEL scheme and the given number as host
463 pub fn tel(number: impl Into<String>) -> Self {
464 Self::new(Scheme::Tel, Host::domain(number))
465 }
466
467 /// Create a new HTTP URI
468 ///
469 /// # Parameters
470 /// - `host`: The domain name
471 ///
472 /// # Returns
473 /// A new URI with HTTP scheme and the given domain host
474 ///
475 /// # Examples
476 ///
477 /// ```
478 /// use rvoip_sip_core::prelude::*;
479 ///
480 /// let uri = Uri::http("example.com");
481 /// assert_eq!(uri.to_string(), "http:example.com");
482 /// ```
483 pub fn http(host: impl Into<String>) -> Self {
484 Self::new(Scheme::Http, Host::domain(host))
485 }
486
487 /// Create a new HTTPS URI
488 ///
489 /// # Parameters
490 /// - `host`: The domain name
491 ///
492 /// # Returns
493 /// A new URI with HTTPS scheme and the given domain host
494 ///
495 /// # Examples
496 ///
497 /// ```
498 /// use rvoip_sip_core::prelude::*;
499 ///
500 /// let uri = Uri::https("example.com");
501 /// assert_eq!(uri.to_string(), "https:example.com");
502 /// ```
503 pub fn https(host: impl Into<String>) -> Self {
504 Self::new(Scheme::Https, Host::domain(host))
505 }
506
507 /// Create a new URI with a custom scheme by storing the entire URI string
508 /// This is used for schemes that are not explicitly supported (like http, https)
509 /// but need to be preserved in the Call-Info header
510 ///
511 /// # Parameters
512 /// - `uri_string`: The full URI string
513 ///
514 /// # Returns
515 /// A new URI with the appropriate scheme and preserved raw string
516 pub fn custom(uri_string: impl Into<String>) -> Self {
517 let uri_string = uri_string.into();
518
519 // Try to extract the scheme if possible
520 let scheme = if let Some(colon_pos) = uri_string.find(':') {
521 let scheme_str = &uri_string[0..colon_pos];
522 match scheme_str.to_lowercase().as_str() {
523 "http" => Scheme::Http,
524 "https" => Scheme::Https,
525 "tel" => Scheme::Tel,
526 "sip" => Scheme::Sip,
527 "sips" => Scheme::Sips,
528 _ => Scheme::Custom(scheme_str.to_string()), // Preserve custom schemes
529 }
530 } else {
531 // If no scheme found, create a custom scheme from the whole string
532 Scheme::Custom(uri_string.clone())
533 };
534
535 Uri {
536 scheme,
537 user: None,
538 password: None,
539 host: Host::domain("unknown.host"), // Placeholder host
540 port: None,
541 parameters: Vec::new(),
542 headers: HashMap::new(),
543 raw_uri: Some(uri_string),
544 }
545 }
546
547 /// Check if this URI has a custom scheme (non-SIP)
548 ///
549 /// # Returns
550 /// `true` if this is a custom URI, `false` otherwise
551 pub fn is_custom(&self) -> bool {
552 self.raw_uri.is_some()
553 }
554
555 /// Get the raw URI string if this is a custom URI
556 ///
557 /// # Returns
558 /// The raw URI string if this is a custom URI, `None` otherwise
559 pub fn as_raw_uri(&self) -> Option<&str> {
560 self.raw_uri.as_deref()
561 }
562
563 /// Get the username part of the URI, if present
564 ///
565 /// # Returns
566 /// The username as a string slice, or `None` if not set
567 pub fn username(&self) -> Option<&str> {
568 self.user.as_deref()
569 }
570
571 /// Set the user part of the URI
572 ///
573 /// # Parameters
574 /// - `user`: The user part to set
575 ///
576 /// # Returns
577 /// Self for method chaining
578 pub fn with_user(mut self, user: impl Into<String>) -> Self {
579 self.user = Some(user.into());
580 self
581 }
582
583 /// Set the password part of the URI (deprecated in SIP)
584 ///
585 /// # Parameters
586 /// - `password`: The password to set
587 ///
588 /// # Returns
589 /// Self for method chaining
590 ///
591 /// # Note
592 /// Passwords in SIP URIs are deprecated for security reasons,
593 /// but supported for compatibility.
594 pub fn with_password(mut self, password: impl Into<String>) -> Self {
595 self.password = Some(password.into());
596 self
597 }
598
599 /// Set the port part of the URI
600 ///
601 /// # Parameters
602 /// - `port`: The port number
603 ///
604 /// # Returns
605 /// Self for method chaining
606 pub fn with_port(mut self, port: u16) -> Self {
607 self.port = Some(port);
608 self
609 }
610
611 /// Add a parameter to the URI
612 ///
613 /// # Parameters
614 /// - `param`: The parameter to add
615 ///
616 /// # Returns
617 /// Self for method chaining
618 ///
619 /// # Examples
620 /// ```
621 /// use rvoip_sip_core::prelude::*;
622 ///
623 /// let uri = Uri::sip("example.com")
624 /// .with_parameter(Param::transport("tcp"))
625 /// .with_parameter(Param::ttl(60));
626 ///
627 /// assert_eq!(uri.to_string(), "sip:example.com;transport=tcp;ttl=60");
628 /// ```
629 pub fn with_parameter(mut self, param: Param) -> Self {
630 self.parameters.push(param);
631 self
632 }
633
634 /// Add a header to the URI
635 ///
636 /// # Parameters
637 /// - `key`: The header name
638 /// - `value`: The header value
639 ///
640 /// # Returns
641 /// Self for method chaining
642 ///
643 /// # Examples
644 /// ```
645 /// use rvoip_sip_core::prelude::*;
646 ///
647 /// let uri = Uri::sip("example.com")
648 /// .with_header("subject", "Meeting")
649 /// .with_header("priority", "urgent");
650 ///
651 /// // Headers are added to the URI string
652 /// let uri_str = uri.to_string();
653 /// assert!(uri_str.contains("subject=Meeting"));
654 /// assert!(uri_str.contains("priority=urgent"));
655 /// ```
656 pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
657 self.headers.insert(key.into(), value.into());
658 self
659 }
660
661 /// Returns the transport parameter if present
662 ///
663 /// # Returns
664 /// The transport value as a string slice, or `None` if not set
665 ///
666 /// # Examples
667 /// ```
668 /// use rvoip_sip_core::prelude::*;
669 ///
670 /// let uri = Uri::sip("example.com")
671 /// .with_parameter(Param::transport("tcp"));
672 ///
673 /// assert_eq!(uri.transport(), Some("tcp"));
674 /// ```
675 pub fn transport(&self) -> Option<&str> {
676 self.parameters.iter().find_map(|p| match p {
677 Param::Transport(val) => Some(val.as_str()),
678 _ => None,
679 })
680 }
681
682 /// Returns the user=phone parameter if present
683 ///
684 /// # Returns
685 /// `true` if the URI has the user=phone parameter, `false` otherwise
686 ///
687 /// # Examples
688 /// ```
689 /// use rvoip_sip_core::prelude::*;
690 /// use std::str::FromStr;
691 ///
692 /// let uri = Uri::from_str("sip:+12125551212@example.com;user=phone").unwrap();
693 /// assert!(uri.is_phone_number());
694 ///
695 /// let uri = Uri::sip("example.com").with_user("alice");
696 /// assert!(!uri.is_phone_number());
697 /// ```
698 pub fn is_phone_number(&self) -> bool {
699 self.parameters.iter().any(|p| match p {
700 Param::User(val) if val == "phone" => true,
701 _ => false,
702 })
703 }
704}
705
706impl fmt::Display for Uri {
707 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
708 // If this is a custom URI, just output the raw string
709 if let Some(raw_uri) = &self.raw_uri {
710 return f.write_str(raw_uri);
711 }
712
713 // Normal URI formatting
714 write!(f, "{}:", self.scheme)?;
715
716 // User info (username and optional password)
717 if let Some(ref user) = self.user {
718 let escaped_user = escape_user_info(user);
719 write!(f, "{}", escaped_user)?;
720
721 if let Some(ref password) = self.password {
722 let escaped_password = escape_user_info(password);
723 write!(f, ":{}", escaped_password)?;
724 }
725
726 write!(f, "@")?;
727 }
728
729 // Host (domain or IP address)
730 match &self.host {
731 Host::Domain(domain) => write!(f, "{}", domain)?,
732 Host::Address(IpAddr::V4(addr)) => write!(f, "{}", addr)?,
733 Host::Address(IpAddr::V6(addr)) => write!(f, "[{}]", addr)?,
734 }
735
736 // Optional port (only if not 0)
737 if let Some(port) = self.port {
738 // Don't show port 0
739 if port > 0 {
740 write!(f, ":{}", port)?;
741 }
742 }
743
744 // Parameters (;key=value or ;key)
745 for param in &self.parameters {
746 write!(f, ";{}", param)?;
747 }
748
749 // Headers (?key=value&key=value)
750 if !self.headers.is_empty() {
751 let mut first = true;
752 for (key, value) in &self.headers {
753 if first {
754 write!(f, "?")?;
755 first = false;
756 } else {
757 write!(f, "&")?;
758 }
759
760 // URL-encode key and value
761 write!(f, "{}={}", escape_param(key), escape_param(value))?;
762 }
763 }
764
765 Ok(())
766 }
767}
768
769// Use the internal nom parser for FromStr
770impl FromStr for Uri {
771 type Err = Error;
772
773 fn from_str(s: &str) -> Result<Self> {
774 // Use the nom parser from the parser module
775 match all_consuming(parse_uri)(s.as_bytes()) {
776 Ok((_rem, uri)) => Ok(uri),
777 Err(e) => {
778 // For HTTP and HTTPS URIs, create a special case
779 if s.starts_with("http://") || s.starts_with("https://") {
780 let scheme = if s.starts_with("https://") {
781 Scheme::Https
782 } else {
783 Scheme::Http
784 };
785
786 // Extract host from the URL (simple implementation)
787 let without_scheme = if s.starts_with("https://") {
788 &s[8..]
789 } else {
790 &s[7..]
791 };
792
793 let host_part = without_scheme
794 .split('/')
795 .next()
796 .unwrap_or(without_scheme);
797
798 return Ok(Uri {
799 scheme,
800 user: None,
801 password: None,
802 host: Host::domain(host_part),
803 port: None,
804 parameters: Vec::new(),
805 headers: HashMap::new(),
806 raw_uri: Some(s.to_string()),
807 });
808 }
809
810 // Otherwise return the original error
811 Err(Error::from(e.to_owned())) // Convert nom::Err to crate::error::Error
812 }
813 }
814 }
815}
816
817// Use the internal nom parser for From<&str>
818impl<'a> From<&'a str> for Uri {
819 fn from(s: &'a str) -> Self {
820 // Attempt to parse using the internal nom parser.
821 // If it fails, fall back to a custom URI with the raw string.
822 Uri::from_str(s).unwrap_or_else(|_| {
823 // If parsing fails, create a custom URI to preserve the string
824 Uri::custom(s)
825 })
826 }
827}
828
829// Parser-related functions need to be reimplemented or imported from parser module
830// For now we'll just include the basic utility functions used by the URI implementation
831
832// --- Helper functions (escape/unescape, validation) ---
833
834/// Escape URI user info component according to RFC 3261
835///
836/// This escapes characters in the user info component (username/password)
837/// using percent-encoding as specified in the RFC.
838///
839/// # Parameters
840/// - `s`: The string to escape
841///
842/// # Returns
843/// The escaped string
844fn escape_user_info(s: &str) -> String {
845 let mut result = String::with_capacity(s.len() * 3); // Worst case: all chars need escaping (×3)
846
847 for c in s.chars() {
848 match c {
849 'a'..='z' | 'A'..='Z' | '0'..='9' |
850 '-' | '_' | '.' | '!' | '~' | '*' | '\'' | '(' | ')' => {
851 result.push(c);
852 },
853 _ => {
854 // Escape all other characters
855 for byte in c.to_string().bytes() {
856 result.push('%');
857 result.push_str(&format!("{:02X}", byte));
858 }
859 }
860 }
861 }
862
863 result
864}
865
866/// Unescape URI user info component
867fn unescape_user_info(s: &str) -> Result<String> {
868 let mut result = String::with_capacity(s.len());
869 let mut chars = s.chars().peekable();
870
871 while let Some(c) = chars.next() {
872 if c == '%' {
873 let mut hex = String::with_capacity(2);
874
875 if let Some(h1) = chars.next() {
876 hex.push(h1);
877 } else {
878 return Err(Error::MalformedUriComponent {
879 component: "user info".to_string(),
880 message: "Incomplete percent encoding".to_string()
881 });
882 }
883
884 if let Some(h2) = chars.next() {
885 hex.push(h2);
886 } else {
887 return Err(Error::MalformedUriComponent {
888 component: "user info".to_string(),
889 message: "Incomplete percent encoding".to_string()
890 });
891 }
892
893 if let Ok(byte) = u8::from_str_radix(&hex, 16) {
894 result.push(byte as char);
895 } else {
896 return Err(Error::MalformedUriComponent {
897 component: "user info".to_string(),
898 message: format!("Invalid percent encoding: %{}", hex)
899 });
900 }
901 } else {
902 result.push(c);
903 }
904 }
905
906 Ok(result)
907}
908
909/// Escape URI parameters and headers
910fn escape_param(s: &str) -> String {
911 let mut result = String::with_capacity(s.len() * 3);
912
913 for c in s.chars() {
914 match c {
915 'a'..='z' | 'A'..='Z' | '0'..='9' |
916 '-' | '_' | '.' | '!' | '~' | '*' | '\'' | '(' | ')' | '+' => {
917 result.push(c);
918 },
919 _ => {
920 // Escape all other characters
921 for byte in c.to_string().bytes() {
922 result.push('%');
923 result.push_str(&format!("{:02X}", byte));
924 }
925 }
926 }
927 }
928
929 result
930}
931
932/// Unescape URI parameters and headers
933fn unescape_param(s: &str) -> Result<String> {
934 unescape_user_info(s) // Same algorithm
935}
936
937/// Check if a string is a valid IPv4 address
938fn is_valid_ipv4(s: &str) -> bool {
939 let parts: Vec<&str> = s.split('.').collect();
940
941 if parts.len() != 4 {
942 return false;
943 }
944
945 for part in parts {
946 match part.parse::<u8>() {
947 Ok(_) => continue,
948 Err(_) => return false,
949 }
950 }
951
952 true
953}
954
955/// Check if a string is a valid IPv6 address (simplified validation)
956fn is_valid_ipv6(s: &str) -> bool {
957 // Check for basic IPv6 format
958 let parts = s.split(':').collect::<Vec<&str>>();
959
960 // IPv6 has 8 parts max, or fewer if contains ::
961 if parts.len() > 8 {
962 return false;
963 }
964
965 // Check for empty parts (::)
966 let empty_parts = parts.iter().filter(|p| p.is_empty()).count();
967
968 // Handle :: (consecutive colons)
969 if empty_parts > 0 {
970 if empty_parts > 2 || (empty_parts == 2 && !s.contains("::")) {
971 return false;
972 }
973 }
974
975 // Validate each part
976 for part in parts {
977 if part.is_empty() {
978 continue; // Empty part due to ::
979 }
980
981 // Check if it's an IPv4 address in the last part (IPv4-mapped IPv6)
982 if part.contains('.') {
983 return is_valid_ipv4(part);
984 }
985
986 // Each part should be a valid hex number with at most 4 digits
987 if part.len() > 4 || !part.chars().all(|c| c.is_ascii_hexdigit()) {
988 return false;
989 }
990 }
991
992 true
993}