rtc_ice/url/
mod.rs

1#[cfg(test)]
2mod url_test;
3
4use std::borrow::Cow;
5use std::convert::From;
6use std::fmt;
7
8use shared::error::*;
9
10/// The type of server used in the ice.URL structure.
11#[derive(Default, PartialEq, Eq, Debug, Copy, Clone)]
12pub enum SchemeType {
13    /// The URL represents a STUN server.
14    Stun,
15
16    /// The URL represents a STUNS (secure) server.
17    Stuns,
18
19    /// The URL represents a TURN server.
20    Turn,
21
22    /// The URL represents a TURNS (secure) server.
23    Turns,
24
25    #[default]
26    /// Default public constant to use for "enum" like struct comparisons when no value was defined.
27    Unknown,
28}
29
30impl From<&str> for SchemeType {
31    /// Defines a procedure for creating a new `SchemeType` from a raw
32    /// string naming the scheme type.
33    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/// The transport protocol type that is used in the `ice::url::Url` structure.
58#[derive(Default, PartialEq, Eq, Debug, Copy, Clone)]
59pub enum ProtoType {
60    /// The URL uses a UDP transport.
61    #[default]
62    Udp,
63
64    /// The URL uses a TCP transport.
65    Tcp,
66
67    Unknown,
68}
69
70// defines a procedure for creating a new ProtoType from a raw
71// string naming the transport protocol type.
72impl From<&str> for ProtoType {
73    // NewSchemeType defines a procedure for creating a new SchemeType from a raw
74    // string naming the scheme type.
75    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/// Represents a STUN (rfc7064) or TURN (rfc7065) URL.
96#[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    /// Parses a STUN or TURN urls following the ABNF syntax described in
127    /// [IETF rfc-7064](https://tools.ietf.org/html/rfc7064) and
128    /// [IETF rfc-7065](https://tools.ietf.org/html/rfc7065) respectively.
129    pub fn parse_url(raw: &str) -> Result<Self> {
130        // work around for url crate
131        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    /*
230    fn parse_proto(raw:&str) ->Result<ProtoType> {
231        let qArgs= raw.split('=');
232        if qArgs.len() != 2 {
233            return Err(Error::ErrInvalidQuery.into());
234        }
235
236        var proto ProtoType
237        if rawProto := qArgs.Get("transport"); rawProto != "" {
238            if proto = NewProtoType(rawProto); proto == ProtoType(0) {
239                return ProtoType(Unknown), ErrProtoType
240            }
241            return proto, nil
242        }
243
244        if len(qArgs) > 0 {
245            return ProtoType(Unknown), ErrInvalidQuery
246        }
247
248        return proto, nil
249    }*/
250
251    /// Returns whether the this URL's scheme describes secure scheme or not.
252    #[must_use]
253    pub fn is_secure(&self) -> bool {
254        self.scheme == SchemeType::Stuns || self.scheme == SchemeType::Turns
255    }
256}