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