1#[cfg(test)]
2mod url_test;
3
4use std::borrow::Cow;
5use std::convert::From;
6use std::fmt;
7
8use shared::error::*;
9
10#[derive(Default, PartialEq, Eq, Debug, Copy, Clone)]
12#[non_exhaustive]
13pub enum SchemeType {
14 Stun,
16
17 Stuns,
19
20 Turn,
22
23 Turns,
25
26 #[default]
27 Unknown,
30}
31
32impl From<&str> for SchemeType {
33 fn from(raw: &str) -> Self {
36 match raw {
37 "stun" => Self::Stun,
38 "stuns" => Self::Stuns,
39 "turn" => Self::Turn,
40 "turns" => Self::Turns,
41 _ => Self::Unknown,
42 }
43 }
44}
45
46impl fmt::Display for SchemeType {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 let s = match *self {
49 SchemeType::Stun => "stun",
50 SchemeType::Stuns => "stuns",
51 SchemeType::Turn => "turn",
52 SchemeType::Turns => "turns",
53 SchemeType::Unknown => "unknown",
54 };
55 write!(f, "{s}")
56 }
57}
58
59#[derive(Default, PartialEq, Eq, Debug, Copy, Clone)]
61#[non_exhaustive]
62pub enum ProtoType {
63 #[default]
65 Udp,
66
67 Tcp,
69
70 Unknown,
72}
73
74impl From<&str> for ProtoType {
77 fn from(raw: &str) -> Self {
80 match raw {
81 "udp" => Self::Udp,
82 "tcp" => Self::Tcp,
83 _ => Self::Unknown,
84 }
85 }
86}
87
88impl fmt::Display for ProtoType {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 let s = match *self {
91 Self::Udp => "udp",
92 Self::Tcp => "tcp",
93 Self::Unknown => "unknown",
94 };
95 write!(f, "{s}")
96 }
97}
98
99#[derive(Debug, Clone, Default)]
101pub struct Url {
102 pub scheme: SchemeType,
104 pub host: String,
106 pub port: u16,
108 pub username: String,
110 pub password: String,
112 pub proto: ProtoType,
114}
115
116impl fmt::Display for Url {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 let host = if self.host.contains("::") {
119 "[".to_owned() + self.host.as_str() + "]"
120 } else {
121 self.host.clone()
122 };
123 if self.scheme == SchemeType::Turn || self.scheme == SchemeType::Turns {
124 write!(
125 f,
126 "{}:{}:{}?transport={}",
127 self.scheme, host, self.port, self.proto
128 )
129 } else {
130 write!(f, "{}:{}:{}", self.scheme, host, self.port)
131 }
132 }
133}
134
135impl Url {
136 pub fn parse_url(raw: &str) -> Result<Self> {
140 if raw.contains("//") {
142 return Err(Error::ErrInvalidUrl);
143 }
144
145 let mut s = raw.to_string();
146 let pos = raw.find(':');
147 if let Some(p) = pos {
148 s.replace_range(p..=p, "://");
149 } else {
150 return Err(Error::ErrSchemeType);
151 }
152
153 let raw_parts = url::Url::parse(&s)?;
154
155 let scheme = raw_parts.scheme().into();
156
157 let host = if let Some(host) = raw_parts.host_str() {
158 host.trim()
159 .trim_start_matches('[')
160 .trim_end_matches(']')
161 .to_owned()
162 } else {
163 return Err(Error::ErrHost);
164 };
165
166 let port = if let Some(port) = raw_parts.port() {
167 port
168 } else if scheme == SchemeType::Stun || scheme == SchemeType::Turn {
169 3478
170 } else {
171 5349
172 };
173
174 let mut q_args = raw_parts.query_pairs();
175 let proto = match scheme {
176 SchemeType::Stun => {
177 if q_args.count() > 0 {
178 return Err(Error::ErrStunQuery);
179 }
180 ProtoType::Udp
181 }
182 SchemeType::Stuns => {
183 if q_args.count() > 0 {
184 return Err(Error::ErrStunQuery);
185 }
186 ProtoType::Tcp
187 }
188 SchemeType::Turn => {
189 if q_args.count() > 1 {
190 return Err(Error::ErrInvalidQuery);
191 }
192 if let Some((key, value)) = q_args.next() {
193 if key == Cow::Borrowed("transport") {
194 let proto: ProtoType = value.as_ref().into();
195 if proto == ProtoType::Unknown {
196 return Err(Error::ErrProtoType);
197 }
198 proto
199 } else {
200 return Err(Error::ErrInvalidQuery);
201 }
202 } else {
203 ProtoType::Udp
204 }
205 }
206 SchemeType::Turns => {
207 if q_args.count() > 1 {
208 return Err(Error::ErrInvalidQuery);
209 }
210 if let Some((key, value)) = q_args.next() {
211 if key == Cow::Borrowed("transport") {
212 let proto: ProtoType = value.as_ref().into();
213 if proto == ProtoType::Unknown {
214 return Err(Error::ErrProtoType);
215 }
216 proto
217 } else {
218 return Err(Error::ErrInvalidQuery);
219 }
220 } else {
221 ProtoType::Tcp
222 }
223 }
224 SchemeType::Unknown => {
225 return Err(Error::ErrSchemeType);
226 }
227 };
228
229 Ok(Self {
230 scheme,
231 host,
232 port,
233 username: "".to_owned(),
234 password: "".to_owned(),
235 proto,
236 })
237 }
238
239 #[must_use]
263 pub fn is_secure(&self) -> bool {
264 self.scheme == SchemeType::Stuns || self.scheme == SchemeType::Turns
265 }
266}