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