Skip to main content

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)]
12#[non_exhaustive]
13pub enum SchemeType {
14    /// The URL represents a STUN server.
15    Stun,
16
17    /// The URL represents a STUNS (secure) server.
18    Stuns,
19
20    /// The URL represents a TURN server.
21    Turn,
22
23    /// The URL represents a TURNS (secure) server.
24    Turns,
25
26    #[default]
27    /// Default public constant to use for "enum" like struct comparisons when no value was defined.
28    /// A scheme or transport this crate does not recognise.
29    Unknown,
30}
31
32impl From<&str> for SchemeType {
33    /// Defines a procedure for creating a new `SchemeType` from a raw
34    /// string naming the scheme type.
35    fn from(raw: &str) -> Self {
36        match raw {
37            "stun" => Self::Stun,
38            "stuns" => Self::Stuns,
39            "turn" => Self::Turn,
40            "turns" => Self::Turns,
41            _ => Self::Unknown,
42        }
43    }
44}
45
46impl fmt::Display for SchemeType {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        let s = match *self {
49            SchemeType::Stun => "stun",
50            SchemeType::Stuns => "stuns",
51            SchemeType::Turn => "turn",
52            SchemeType::Turns => "turns",
53            SchemeType::Unknown => "unknown",
54        };
55        write!(f, "{s}")
56    }
57}
58
59/// The transport protocol type that is used in the `ice::url::Url` structure.
60#[derive(Default, PartialEq, Eq, Debug, Copy, Clone)]
61#[non_exhaustive]
62pub enum ProtoType {
63    /// The URL uses a UDP transport.
64    #[default]
65    Udp,
66
67    /// The URL uses a TCP transport.
68    Tcp,
69
70    /// A transport this crate does not recognise.
71    Unknown,
72}
73
74// defines a procedure for creating a new ProtoType from a raw
75// string naming the transport protocol type.
76impl From<&str> for ProtoType {
77    // NewSchemeType defines a procedure for creating a new SchemeType from a raw
78    // string naming the scheme type.
79    fn from(raw: &str) -> Self {
80        match raw {
81            "udp" => Self::Udp,
82            "tcp" => Self::Tcp,
83            _ => Self::Unknown,
84        }
85    }
86}
87
88impl fmt::Display for ProtoType {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        let s = match *self {
91            Self::Udp => "udp",
92            Self::Tcp => "tcp",
93            Self::Unknown => "unknown",
94        };
95        write!(f, "{s}")
96    }
97}
98
99/// Represents a STUN (rfc7064) or TURN (rfc7065) URL.
100#[derive(Debug, Clone, Default)]
101pub struct Url {
102    /// The URL scheme: `stun`, `stuns`, `turn` or `turns`.
103    pub scheme: SchemeType,
104    /// The server host name or address.
105    pub host: String,
106    /// The server port; defaults to 3478, or 5349 for the secure schemes.
107    pub port: u16,
108    /// The TURN username, for `turn:`/`turns:` URLs.
109    pub username: String,
110    /// The TURN credential.
111    pub password: String,
112    /// The transport to reach the server over, from the URL's `?transport=` parameter.
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    /// Parses a STUN or TURN urls following the ABNF syntax described in
137    /// [IETF rfc-7064](https://tools.ietf.org/html/rfc7064) and
138    /// [IETF rfc-7065](https://tools.ietf.org/html/rfc7065) respectively.
139    pub fn parse_url(raw: &str) -> Result<Self> {
140        // work around for url crate
141        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    /*
240    fn parse_proto(raw:&str) ->Result<ProtoType> {
241        let qArgs= raw.split('=');
242        if qArgs.len() != 2 {
243            return Err(Error::ErrInvalidQuery.into());
244        }
245
246        var proto ProtoType
247        if rawProto := qArgs.Get("transport"); rawProto != "" {
248            if proto = NewProtoType(rawProto); proto == ProtoType(0) {
249                return ProtoType(Unknown), ErrProtoType
250            }
251            return proto, nil
252        }
253
254        if len(qArgs) > 0 {
255            return ProtoType(Unknown), ErrInvalidQuery
256        }
257
258        return proto, nil
259    }*/
260
261    /// Returns whether the this URL's scheme describes secure scheme or not.
262    #[must_use]
263    pub fn is_secure(&self) -> bool {
264        self.scheme == SchemeType::Stuns || self.scheme == SchemeType::Turns
265    }
266}