1#[cfg(test)]
2mod url_test;
3
4use std::borrow::Cow;
5use std::convert::From;
6use std::fmt;
7
8use crate::error::*;
9
10#[derive(PartialEq, Eq, Debug, Copy, Clone)]
12pub enum SchemeType {
13 Stun,
15
16 Stuns,
18
19 Turn,
21
22 Turns,
24
25 Unknown,
27}
28
29impl Default for SchemeType {
30 fn default() -> Self {
31 Self::Unknown
32 }
33}
34
35impl From<&str> for SchemeType {
36 fn from(raw: &str) -> Self {
39 match raw {
40 "stun" => Self::Stun,
41 "stuns" => Self::Stuns,
42 "turn" => Self::Turn,
43 "turns" => Self::Turns,
44 _ => Self::Unknown,
45 }
46 }
47}
48
49impl fmt::Display for SchemeType {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 let s = match *self {
52 SchemeType::Stun => "stun",
53 SchemeType::Stuns => "stuns",
54 SchemeType::Turn => "turn",
55 SchemeType::Turns => "turns",
56 SchemeType::Unknown => "unknown",
57 };
58 write!(f, "{s}")
59 }
60}
61
62#[derive(PartialEq, Eq, Debug, Copy, Clone)]
64pub enum ProtoType {
65 Udp,
67
68 Tcp,
70
71 Unknown,
72}
73
74impl Default for ProtoType {
75 fn default() -> Self {
76 Self::Udp
77 }
78}
79
80impl From<&str> for ProtoType {
83 fn from(raw: &str) -> Self {
86 match raw {
87 "udp" => Self::Udp,
88 "tcp" => Self::Tcp,
89 _ => Self::Unknown,
90 }
91 }
92}
93
94impl fmt::Display for ProtoType {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 let s = match *self {
97 Self::Udp => "udp",
98 Self::Tcp => "tcp",
99 Self::Unknown => "unknown",
100 };
101 write!(f, "{s}")
102 }
103}
104
105#[derive(Debug, Clone, Default)]
107pub struct Url {
108 pub scheme: SchemeType,
109 pub host: String,
110 pub port: u16,
111 pub username: String,
112 pub password: String,
113 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}