1use super::BuildError;
2use crate::{
3 component::IAuthority,
4 imp::{AuthMeta, HostMeta, Meta},
5 parse,
6 pct_enc::{
7 encoder::{IRegName, Port, RegName},
8 EStr,
9 },
10};
11use alloc::string::String;
12use core::{fmt::Write, num::NonZeroUsize};
13
14#[cfg(feature = "net")]
15use crate::net::{IpAddr, Ipv4Addr, Ipv6Addr};
16
17pub struct BuilderInner {
18 pub buf: String,
19 pub meta: Meta,
20}
21
22impl BuilderInner {
23 pub fn push_scheme(&mut self, v: &str) {
24 self.buf.push_str(v);
25 self.meta.scheme_end = NonZeroUsize::new(self.buf.len());
26 self.buf.push(':');
27 }
28
29 pub fn start_authority(&mut self) {
30 self.buf.push_str("//");
31 }
32
33 pub fn push_authority(&mut self, v: IAuthority<'_>) {
34 self.buf.push_str("//");
35 let start = self.buf.len();
36 self.buf.push_str(v.as_str());
37
38 let mut meta = v.meta();
39 meta.host_bounds.0 += start;
40 meta.host_bounds.1 += start;
41 self.meta.auth_meta = Some(meta);
42 }
43
44 pub fn push_userinfo(&mut self, v: &str) {
45 self.buf.push_str(v);
46 self.buf.push('@');
47 }
48
49 pub fn push_host(&mut self, meta: HostMeta, f: impl FnOnce(&mut String)) {
50 let start = self.buf.len();
51 f(&mut self.buf);
52 self.meta.auth_meta = Some(AuthMeta {
53 host_bounds: (start, self.buf.len()),
54 host_meta: meta,
55 });
56 }
57
58 pub fn push_path(&mut self, v: &str) {
59 self.meta.path_bounds.0 = self.buf.len();
60 self.buf.push_str(v);
61 self.meta.path_bounds.1 = self.buf.len();
62 }
63
64 pub fn push_query(&mut self, v: &str) {
65 self.buf.push('?');
66 self.buf.push_str(v);
67 self.meta.query_end = NonZeroUsize::new(self.buf.len());
68 }
69
70 pub fn push_fragment(&mut self, v: &str) {
71 self.buf.push('#');
72 self.buf.push_str(v);
73 }
74
75 pub fn validate(&self) -> Result<(), BuildError> {
76 fn first_segment_contains_colon(path: &str) -> bool {
77 path.split_once('/').map_or(path, |x| x.0).contains(':')
78 }
79
80 let (start, end) = self.meta.path_bounds;
81 let path = &self.buf[start..end];
82
83 if self.meta.auth_meta.is_some() {
84 if !path.is_empty() && !path.starts_with('/') {
85 return Err(BuildError::NonemptyRootlessPath);
86 }
87 } else {
88 if path.starts_with("//") {
89 return Err(BuildError::PathStartsWithDoubleSlash);
90 }
91 if self.meta.scheme_end.is_none() && first_segment_contains_colon(path) {
92 return Err(BuildError::FirstPathSegmentContainsColon);
93 }
94 }
95 Ok(())
96 }
97}
98
99pub trait AsHost<'a> {
100 fn push_to(self, b: &mut BuilderInner);
101}
102
103#[cfg(feature = "net")]
104impl<'a> AsHost<'a> for Ipv4Addr {
105 fn push_to(self, b: &mut BuilderInner) {
106 b.push_host(HostMeta::Ipv4(self), |buf| {
107 write!(buf, "{self}").unwrap();
108 });
109 }
110}
111
112#[cfg(feature = "net")]
113impl<'a> AsHost<'a> for Ipv6Addr {
114 fn push_to(self, b: &mut BuilderInner) {
115 b.push_host(HostMeta::Ipv6(self), |buf| {
116 write!(buf, "[{self}]").unwrap();
117 });
118 }
119}
120
121#[cfg(feature = "net")]
122impl<'a> AsHost<'a> for IpAddr {
123 fn push_to(self, b: &mut BuilderInner) {
124 match self {
125 IpAddr::V4(addr) => addr.push_to(b),
126 IpAddr::V6(addr) => addr.push_to(b),
127 }
128 }
129}
130
131impl<'a> AsHost<'a> for &'a EStr<RegName> {
132 #[inline]
133 fn push_to(self, b: &mut BuilderInner) {
134 self.cast::<IRegName>().push_to(b);
135 }
136}
137
138impl<'a> AsHost<'a> for &'a EStr<IRegName> {
139 fn push_to(self, b: &mut BuilderInner) {
140 let meta = parse::parse_v4_or_reg_name(self.as_str().as_bytes());
141 b.push_host(meta, |buf| {
142 buf.push_str(self.as_str());
143 });
144 }
145}
146
147pub trait WithEncoder<E> {}
148
149#[cfg(feature = "net")]
150impl<E> WithEncoder<E> for Ipv4Addr {}
151#[cfg(feature = "net")]
152impl<E> WithEncoder<E> for Ipv6Addr {}
153#[cfg(feature = "net")]
154impl<E> WithEncoder<E> for IpAddr {}
155
156impl WithEncoder<RegName> for &EStr<RegName> {}
157impl WithEncoder<IRegName> for &EStr<IRegName> {}
158
159pub trait AsPort {
160 fn push_to(self, buf: &mut String);
161}
162
163impl AsPort for u16 {
164 fn push_to(self, buf: &mut String) {
165 write!(buf, ":{self}").unwrap();
166 }
167}
168
169impl AsPort for &EStr<Port> {
170 fn push_to(self, buf: &mut String) {
171 buf.push(':');
172 buf.push_str(self.as_str());
173 }
174}