Coverage Report

Created: 2025-06-16 06:50

/rust/registry/src/index.crates.io-6f17d22bba15001f/urlencoding-2.1.3/src/dec.rs
Line
Count
Source (jump to first uncovered line)
1
use std::borrow::Cow;
2
use std::string::FromUtf8Error;
3
4
#[inline]
5
12.0M
pub(crate) fn from_hex_digit(digit: u8) -> Option<u8> {
6
12.0M
    match digit {
7
11.9M
        b'0'..=b'9' => Some(digit - b'0'),
8
5.68M
        b'A'..=b'F' => Some(digit - b'A' + 10),
9
46.5k
        b'a'..=b'f' => Some(digit - b'a' + 10),
10
109k
        _ => None,
11
    }
12
12.0M
}
13
14
/// Decode percent-encoded string assuming UTF-8 encoding.
15
///
16
/// If you need a `String`, call `.into_owned()` (not `.to_owned()`).
17
///
18
/// Unencoded `+` is preserved literally, and _not_ changed to a space.
19
0
pub fn decode(data: &str) -> Result<Cow<str>, FromUtf8Error> {
20
0
    match decode_binary(data.as_bytes()) {
21
0
        Cow::Borrowed(_) => Ok(Cow::Borrowed(data)),
22
0
        Cow::Owned(s) => Ok(Cow::Owned(String::from_utf8(s)?)),
23
    }
24
0
}
25
26
/// Decode percent-encoded string as binary data, in any encoding.
27
///
28
/// Unencoded `+` is preserved literally, and _not_ changed to a space.
29
715k
pub fn decode_binary(data: &[u8]) -> Cow<[u8]> {
30
6.99M
    let offset = data.iter().take_while(|&&c| c != b'%').count();
31
715k
    if offset >= data.len() {
32
378k
        return Cow::Borrowed(data)
33
337k
    }
34
337k
35
337k
    let mut decoded: Vec<u8> = Vec::with_capacity(data.len());
36
337k
    let mut out = NeverRealloc(&mut decoded);
37
337k
38
337k
    let (ascii, mut data) = data.split_at(offset);
39
337k
    out.extend_from_slice(ascii);
40
41
6.38M
    loop {
42
16.5M
        let mut parts = data.splitn(2, |&c| c == b'%');
43
6.38M
        // first the decoded non-% part
44
6.38M
        let non_escaped_part = parts.next().unwrap();
45
6.38M
        let rest = parts.next();
46
6.38M
        if rest.is_none() && out.0.is_empty() {
47
            // if empty there were no '%' in the string
48
0
            return data.into();
49
6.38M
        }
50
6.38M
        out.extend_from_slice(non_escaped_part);
51
6.38M
52
6.38M
        // then decode one %xx
53
6.38M
        match rest {
54
6.07M
            Some(rest) => match rest.get(0..2) {
55
6.05M
                Some(&[first, second]) => match from_hex_digit(first) {
56
5.95M
                    Some(first_val) => match from_hex_digit(second) {
57
5.94M
                        Some(second_val) => {
58
5.94M
                            out.push((first_val << 4) | second_val);
59
5.94M
                            data = &rest[2..];
60
5.94M
                        },
61
10.5k
                        None => {
62
10.5k
                            out.extend_from_slice(&[b'%', first]);
63
10.5k
                            data = &rest[1..];
64
10.5k
                        },
65
                    },
66
98.7k
                    None => {
67
98.7k
                        out.push(b'%');
68
98.7k
                        data = rest;
69
98.7k
                    },
70
                },
71
                _ => {
72
                    // too short
73
19.5k
                    out.push(b'%');
74
19.5k
                    out.extend_from_slice(rest);
75
19.5k
                    break;
76
                },
77
            },
78
317k
            None => break,
79
        }
80
    }
81
337k
    Cow::Owned(decoded)
82
715k
}
83
84
85
struct NeverRealloc<'a, T>(pub &'a mut Vec<T>);
86
87
impl<T> NeverRealloc<'_, T> {
88
    #[inline]
89
6.05M
    pub fn push(&mut self, val: T) {
90
6.05M
        // these branches only exist to remove redundant reallocation code
91
6.05M
        // (the capacity is always sufficient)
92
6.05M
        if self.0.len() != self.0.capacity() {
93
6.05M
            self.0.push(val);
94
6.05M
        }
95
6.05M
    }
96
    #[inline]
97
6.75M
    pub fn extend_from_slice(&mut self, val: &[T]) where T: Clone {
98
6.75M
        if self.0.capacity() - self.0.len() >= val.len() {
99
6.75M
            self.0.extend_from_slice(val);
100
6.75M
        }
101
6.75M
    }
102
}
103
104
#[test]
105
fn dec_borrows() {
106
    assert!(matches!(decode("hello"), Ok(Cow::Borrowed("hello"))));
107
    assert!(matches!(decode("hello%20"), Ok(Cow::Owned(s)) if s == "hello "));
108
    assert!(matches!(decode("%20hello"), Ok(Cow::Owned(s)) if s == " hello"));
109
}