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