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