Coverage Report

Created: 2025-05-07 06:59

/rust/registry/src/index.crates.io-6f17d22bba15001f/http-1.3.1/src/uri/scheme.rs
Line
Count
Source (jump to first uncovered line)
1
use std::convert::TryFrom;
2
use std::fmt;
3
use std::hash::{Hash, Hasher};
4
use std::str::FromStr;
5
6
use bytes::Bytes;
7
8
use super::{ErrorKind, InvalidUri};
9
use crate::byte_str::ByteStr;
10
11
/// Represents the scheme component of a URI
12
#[derive(Clone)]
13
pub struct Scheme {
14
    pub(super) inner: Scheme2,
15
}
16
17
#[derive(Clone, Debug)]
18
pub(super) enum Scheme2<T = Box<ByteStr>> {
19
    None,
20
    Standard(Protocol),
21
    Other(T),
22
}
23
24
#[derive(Copy, Clone, Debug)]
25
pub(super) enum Protocol {
26
    Http,
27
    Https,
28
}
29
30
impl Scheme {
31
    /// HTTP protocol scheme
32
    pub const HTTP: Scheme = Scheme {
33
        inner: Scheme2::Standard(Protocol::Http),
34
    };
35
36
    /// HTTP protocol over TLS.
37
    pub const HTTPS: Scheme = Scheme {
38
        inner: Scheme2::Standard(Protocol::Https),
39
    };
40
41
0
    pub(super) fn empty() -> Self {
42
0
        Scheme {
43
0
            inner: Scheme2::None,
44
0
        }
45
0
    }
46
47
    /// Return a str representation of the scheme
48
    ///
49
    /// # Examples
50
    ///
51
    /// ```
52
    /// # use http::uri::*;
53
    /// let scheme: Scheme = "http".parse().unwrap();
54
    /// assert_eq!(scheme.as_str(), "http");
55
    /// ```
56
    #[inline]
57
0
    pub fn as_str(&self) -> &str {
58
        use self::Protocol::*;
59
        use self::Scheme2::*;
60
61
0
        match self.inner {
62
0
            Standard(Http) => "http",
63
0
            Standard(Https) => "https",
64
0
            Other(ref v) => &v[..],
65
0
            None => unreachable!(),
66
        }
67
0
    }
Unexecuted instantiation: <http::uri::scheme::Scheme>::as_str
Unexecuted instantiation: <http::uri::scheme::Scheme>::as_str
Unexecuted instantiation: <http::uri::scheme::Scheme>::as_str
68
}
69
70
impl<'a> TryFrom<&'a [u8]> for Scheme {
71
    type Error = InvalidUri;
72
    #[inline]
73
0
    fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> {
74
        use self::Scheme2::*;
75
76
0
        match Scheme2::parse_exact(s)? {
77
0
            None => Err(ErrorKind::InvalidScheme.into()),
78
0
            Standard(p) => Ok(Standard(p).into()),
79
            Other(_) => {
80
0
                let bytes = Bytes::copy_from_slice(s);
81
0
82
0
                // Safety: postcondition on parse_exact() means that s and
83
0
                // hence bytes are valid UTF-8.
84
0
                let string = unsafe { ByteStr::from_utf8_unchecked(bytes) };
85
0
86
0
                Ok(Other(Box::new(string)).into())
87
            }
88
        }
89
0
    }
90
}
91
92
impl<'a> TryFrom<&'a str> for Scheme {
93
    type Error = InvalidUri;
94
    #[inline]
95
0
    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
96
0
        TryFrom::try_from(s.as_bytes())
97
0
    }
98
}
99
100
impl FromStr for Scheme {
101
    type Err = InvalidUri;
102
103
0
    fn from_str(s: &str) -> Result<Self, Self::Err> {
104
0
        TryFrom::try_from(s)
105
0
    }
106
}
107
108
impl fmt::Debug for Scheme {
109
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110
0
        fmt::Debug::fmt(self.as_str(), f)
111
0
    }
112
}
113
114
impl fmt::Display for Scheme {
115
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116
0
        f.write_str(self.as_str())
117
0
    }
118
}
119
120
impl AsRef<str> for Scheme {
121
    #[inline]
122
0
    fn as_ref(&self) -> &str {
123
0
        self.as_str()
124
0
    }
125
}
126
127
impl PartialEq for Scheme {
128
0
    fn eq(&self, other: &Scheme) -> bool {
129
        use self::Protocol::*;
130
        use self::Scheme2::*;
131
132
0
        match (&self.inner, &other.inner) {
133
0
            (&Standard(Http), &Standard(Http)) => true,
134
0
            (&Standard(Https), &Standard(Https)) => true,
135
0
            (Other(a), Other(b)) => a.eq_ignore_ascii_case(b),
136
0
            (&None, _) | (_, &None) => unreachable!(),
137
0
            _ => false,
138
        }
139
0
    }
140
}
141
142
impl Eq for Scheme {}
143
144
/// Case-insensitive equality
145
///
146
/// # Examples
147
///
148
/// ```
149
/// # use http::uri::Scheme;
150
/// let scheme: Scheme = "HTTP".parse().unwrap();
151
/// assert_eq!(scheme, *"http");
152
/// ```
153
impl PartialEq<str> for Scheme {
154
0
    fn eq(&self, other: &str) -> bool {
155
0
        self.as_str().eq_ignore_ascii_case(other)
156
0
    }
157
}
158
159
/// Case-insensitive equality
160
impl PartialEq<Scheme> for str {
161
0
    fn eq(&self, other: &Scheme) -> bool {
162
0
        other == self
163
0
    }
164
}
165
166
/// Case-insensitive hashing
167
impl Hash for Scheme {
168
0
    fn hash<H>(&self, state: &mut H)
169
0
    where
170
0
        H: Hasher,
171
0
    {
172
0
        match self.inner {
173
0
            Scheme2::None => (),
174
0
            Scheme2::Standard(Protocol::Http) => state.write_u8(1),
175
0
            Scheme2::Standard(Protocol::Https) => state.write_u8(2),
176
0
            Scheme2::Other(ref other) => {
177
0
                other.len().hash(state);
178
0
                for &b in other.as_bytes() {
179
0
                    state.write_u8(b.to_ascii_lowercase());
180
0
                }
181
            }
182
        }
183
0
    }
Unexecuted instantiation: <http::uri::scheme::Scheme as core::hash::Hash>::hash::<std::hash::random::DefaultHasher>
Unexecuted instantiation: <http::uri::scheme::Scheme as core::hash::Hash>::hash::<_>
184
}
185
186
impl<T> Scheme2<T> {
187
0
    pub(super) fn is_none(&self) -> bool {
188
0
        matches!(*self, Scheme2::None)
189
0
    }
190
}
191
192
// Require the scheme to not be too long in order to enable further
193
// optimizations later.
194
const MAX_SCHEME_LEN: usize = 64;
195
196
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
197
//
198
// SCHEME_CHARS is a table of valid characters in the scheme part of a URI.  An
199
// entry in the table is 0 for invalid characters. For valid characters the
200
// entry is itself (i.e.  the entry for 43 is b'+' because b'+' == 43u8). An
201
// important characteristic of this table is that all entries above 127 are
202
// invalid. This makes all of the valid entries a valid single-byte UTF-8 code
203
// point. This means that a slice of such valid entries is valid UTF-8.
204
#[rustfmt::skip]
205
const SCHEME_CHARS: [u8; 256] = [
206
    //  0      1      2      3      4      5      6      7      8      9
207
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, //   x
208
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, //  1x
209
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, //  2x
210
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, //  3x
211
        0,     0,     0,  b'+',     0,  b'-',  b'.',     0,  b'0',  b'1', //  4x
212
     b'2',  b'3',  b'4',  b'5',  b'6',  b'7',  b'8',  b'9',  b':',     0, //  5x
213
        0,     0,     0,     0,     0,  b'A',  b'B',  b'C',  b'D',  b'E', //  6x
214
     b'F',  b'G',  b'H',  b'I',  b'J',  b'K',  b'L',  b'M',  b'N',  b'O', //  7x
215
     b'P',  b'Q',  b'R',  b'S',  b'T',  b'U',  b'V',  b'W',  b'X',  b'Y', //  8x
216
     b'Z',     0,     0,     0,     0,     0,     0,  b'a',  b'b',  b'c', //  9x
217
     b'd',  b'e',  b'f',  b'g',  b'h',  b'i',  b'j',  b'k',  b'l',  b'm', // 10x
218
     b'n',  b'o',  b'p',  b'q',  b'r',  b's',  b't',  b'u',  b'v',  b'w', // 11x
219
     b'x',  b'y',  b'z',     0,     0,     0,  b'~',     0,     0,     0, // 12x
220
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 13x
221
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 14x
222
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 15x
223
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 16x
224
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 17x
225
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 18x
226
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 19x
227
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 20x
228
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 21x
229
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 22x
230
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 23x
231
        0,     0,     0,     0,     0,     0,     0,     0,     0,     0, // 24x
232
        0,     0,     0,     0,     0,     0                              // 25x
233
];
234
235
impl Scheme2<usize> {
236
    // Postcondition: On all Ok() returns, s is valid UTF-8
237
0
    fn parse_exact(s: &[u8]) -> Result<Scheme2<()>, InvalidUri> {
238
0
        match s {
239
0
            b"http" => Ok(Protocol::Http.into()),
240
0
            b"https" => Ok(Protocol::Https.into()),
241
            _ => {
242
0
                if s.len() > MAX_SCHEME_LEN {
243
0
                    return Err(ErrorKind::SchemeTooLong.into());
244
0
                }
245
246
                // check that each byte in s is a SCHEME_CHARS which implies
247
                // that it is a valid single byte UTF-8 code point.
248
0
                for &b in s {
249
0
                    match SCHEME_CHARS[b as usize] {
250
                        b':' => {
251
                            // Don't want :// here
252
0
                            return Err(ErrorKind::InvalidScheme.into());
253
                        }
254
                        0 => {
255
0
                            return Err(ErrorKind::InvalidScheme.into());
256
                        }
257
0
                        _ => {}
258
                    }
259
                }
260
261
0
                Ok(Scheme2::Other(()))
262
            }
263
        }
264
0
    }
265
266
0
    pub(super) fn parse(s: &[u8]) -> Result<Scheme2<usize>, InvalidUri> {
267
0
        if s.len() >= 7 {
268
            // Check for HTTP
269
0
            if s[..7].eq_ignore_ascii_case(b"http://") {
270
                // Prefix will be striped
271
0
                return Ok(Protocol::Http.into());
272
0
            }
273
0
        }
274
275
0
        if s.len() >= 8 {
276
            // Check for HTTPs
277
0
            if s[..8].eq_ignore_ascii_case(b"https://") {
278
0
                return Ok(Protocol::Https.into());
279
0
            }
280
0
        }
281
282
0
        if s.len() > 3 {
283
0
            for i in 0..s.len() {
284
0
                let b = s[i];
285
0
286
0
                match SCHEME_CHARS[b as usize] {
287
                    b':' => {
288
                        // Not enough data remaining
289
0
                        if s.len() < i + 3 {
290
0
                            break;
291
0
                        }
292
0
293
0
                        // Not a scheme
294
0
                        if &s[i + 1..i + 3] != b"//" {
295
0
                            break;
296
0
                        }
297
0
298
0
                        if i > MAX_SCHEME_LEN {
299
0
                            return Err(ErrorKind::SchemeTooLong.into());
300
0
                        }
301
0
302
0
                        // Return scheme
303
0
                        return Ok(Scheme2::Other(i));
304
                    }
305
                    // Invalid scheme character, abort
306
0
                    0 => break,
307
0
                    _ => {}
308
                }
309
            }
310
0
        }
311
312
0
        Ok(Scheme2::None)
313
0
    }
314
}
315
316
impl Protocol {
317
0
    pub(super) fn len(&self) -> usize {
318
0
        match *self {
319
0
            Protocol::Http => 4,
320
0
            Protocol::Https => 5,
321
        }
322
0
    }
323
}
324
325
impl<T> From<Protocol> for Scheme2<T> {
326
0
    fn from(src: Protocol) -> Self {
327
0
        Scheme2::Standard(src)
328
0
    }
Unexecuted instantiation: <http::uri::scheme::Scheme2<usize> as core::convert::From<http::uri::scheme::Protocol>>::from
Unexecuted instantiation: <http::uri::scheme::Scheme2<()> as core::convert::From<http::uri::scheme::Protocol>>::from
329
}
330
331
#[doc(hidden)]
332
impl From<Scheme2> for Scheme {
333
0
    fn from(src: Scheme2) -> Self {
334
0
        Scheme { inner: src }
335
0
    }
336
}
337
338
#[cfg(test)]
339
mod test {
340
    use super::*;
341
342
    #[test]
343
    fn scheme_eq_to_str() {
344
        assert_eq!(&scheme("http"), "http");
345
        assert_eq!(&scheme("https"), "https");
346
        assert_eq!(&scheme("ftp"), "ftp");
347
        assert_eq!(&scheme("my+funky+scheme"), "my+funky+scheme");
348
    }
349
350
    #[test]
351
    fn invalid_scheme_is_error() {
352
        Scheme::try_from("my_funky_scheme").expect_err("Unexpectedly valid Scheme");
353
354
        // Invalid UTF-8
355
        Scheme::try_from([0xC0].as_ref()).expect_err("Unexpectedly valid Scheme");
356
    }
357
358
    fn scheme(s: &str) -> Scheme {
359
        s.parse().expect(&format!("Invalid scheme: {}", s))
360
    }
361
}