Coverage Report

Created: 2025-08-29 06:18

/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
13.0M
pub(crate) fn from_hex_digit(digit: u8) -> Option<u8> {
6
13.0M
    match digit {
7
12.7M
        b'0'..=b'9' => Some(digit - b'0'),
8
5.74M
        b'A'..=b'F' => Some(digit - b'A' + 10),
9
68.5k
        b'a'..=b'f' => Some(digit - b'a' + 10),
10
313k
        _ => None,
11
    }
12
13.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
636k
pub fn decode_binary(data: &[u8]) -> Cow<[u8]> {
30
5.85M
    let offset = data.iter().take_while(|&&c| c != b'%').count();
31
636k
    if offset >= data.len() {
32
305k
        return Cow::Borrowed(data)
33
330k
    }
34
330k
35
330k
    let mut decoded: Vec<u8> = Vec::with_capacity(data.len());
36
330k
    let mut out = NeverRealloc(&mut decoded);
37
330k
38
330k
    let (ascii, mut data) = data.split_at(offset);
39
330k
    out.extend_from_slice(ascii);
40
41
6.98M
    loop {
42
18.1M
        let mut parts = data.splitn(2, |&c| c == b'%');
43
6.98M
        // first the decoded non-% part
44
6.98M
        let non_escaped_part = parts.next().unwrap();
45
6.98M
        let rest = parts.next();
46
6.98M
        if rest.is_none() && out.0.is_empty() {
47
            // if empty there were no '%' in the string
48
0
            return data.into();
49
6.98M
        }
50
6.98M
        out.extend_from_slice(non_escaped_part);
51
6.98M
52
6.98M
        // then decode one %xx
53
6.98M
        match rest {
54
6.67M
            Some(rest) => match rest.get(0..2) {
55
6.65M
                Some(&[first, second]) => match from_hex_digit(first) {
56
6.37M
                    Some(first_val) => match from_hex_digit(second) {
57
6.34M
                        Some(second_val) => {
58
6.34M
                            out.push((first_val << 4) | second_val);
59
6.34M
                            data = &rest[2..];
60
6.34M
                        },
61
29.5k
                        None => {
62
29.5k
                            out.extend_from_slice(&[b'%', first]);
63
29.5k
                            data = &rest[1..];
64
29.5k
                        },
65
                    },
66
283k
                    None => {
67
283k
                        out.push(b'%');
68
283k
                        data = rest;
69
283k
                    },
70
                },
71
                _ => {
72
                    // too short
73
14.2k
                    out.push(b'%');
74
14.2k
                    out.extend_from_slice(rest);
75
14.2k
                    break;
76
                },
77
            },
78
316k
            None => break,
79
        }
80
    }
81
330k
    Cow::Owned(decoded)
82
636k
}
83
84
85
struct NeverRealloc<'a, T>(pub &'a mut Vec<T>);
86
87
impl<T> NeverRealloc<'_, T> {
88
    #[inline]
89
6.64M
    pub fn push(&mut self, val: T) {
90
6.64M
        // these branches only exist to remove redundant reallocation code
91
6.64M
        // (the capacity is always sufficient)
92
6.64M
        if self.0.len() != self.0.capacity() {
93
6.64M
            self.0.push(val);
94
6.64M
        }
95
6.64M
    }
96
    #[inline]
97
7.36M
    pub fn extend_from_slice(&mut self, val: &[T]) where T: Clone {
98
7.36M
        if self.0.capacity() - self.0.len() >= val.len() {
99
7.36M
            self.0.extend_from_slice(val);
100
7.36M
        }
101
7.36M
    }
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
}