iri_string/percent_encode/decode.rs
1//! Decoders for percent-encoding.
2
3use core::fmt;
4
5#[cfg(feature = "alloc")]
6use alloc::borrow::Cow;
7#[cfg(feature = "alloc")]
8use alloc::string::{FromUtf8Error, String};
9
10use crate::parser::str::strip_decode_xdigits2;
11
12/// Returns the result of [`percent-decode`] algorithm in the WHATWG URL Standard.
13///
14/// [`percent-decode`]: https://url.spec.whatwg.org/#percent-decode
15///
16/// # Examples
17///
18/// ```
19/// use iri_string::percent_encode::decode::decode_whatwg_bytes;
20///
21/// let decoded = decode_whatwg_bytes(b"hello%20world");
22///
23/// assert_eq!(decoded.not_yet_decoded(), &b"hello%20world"[..]);
24///
25/// // This requires `alloc` feature since
26/// // `into_bytes()` returns `Cow<'_, [u8]>`.
27/// # #[cfg(feature = "alloc")]
28/// assert_eq!(decoded.into_bytes(), &b"hello world"[..]);
29/// ```
30#[inline]
31#[must_use]
32pub fn decode_whatwg_bytes(bytes: &[u8]) -> PercentDecodedWhatwgBytes<'_> {
33 PercentDecodedWhatwgBytes::from_raw(bytes)
34}
35
36/// A percent-decoded string based on [`percent-decode`] algorithm of the WHATWG URL standard.
37///
38/// Note that this type does not guarantee that the string is valid
39/// percent-encoded string. The raw string may have stray percent character or
40/// the following digits may be invalid as hexadecimal digits.
41///
42/// Note that comparisons and hashing via std traits (such as `Eq` and `Hash`)
43/// will use the raw value, not the content after decoding.
44///
45/// [`percent-decode`]: https://url.spec.whatwg.org/#percent-decode
46#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
47pub struct PercentDecodedWhatwgBytes<'a> {
48 /// Not-yet-decoded string.
49 not_yet_decoded: &'a [u8],
50}
51
52impl<'a> PercentDecodedWhatwgBytes<'a> {
53 /// Creates a `PercentDecodedWhatwgBytes` from the possibly non-decoded raw bytes.
54 #[inline]
55 #[must_use]
56 fn from_raw(not_yet_decoded: &'a [u8]) -> Self {
57 Self { not_yet_decoded }
58 }
59
60 /// Returns the not-yet-decoded input bytes.
61 ///
62 /// # Examples
63 ///
64 /// ```
65 /// use iri_string::percent_encode::decode::decode_whatwg_bytes;
66 ///
67 /// let decoded = decode_whatwg_bytes(b"hello%20world");
68 ///
69 /// assert_eq!(decoded.not_yet_decoded(), &b"hello%20world"[..]);
70 /// ```
71 #[inline]
72 #[must_use]
73 pub fn not_yet_decoded(&self) -> &'a [u8] {
74 self.not_yet_decoded
75 }
76
77 /// Decodes the bytes as much as possible.
78 ///
79 /// If the string contains a decodable percent-encoded triplet, returns
80 /// a tuple of:
81 ///
82 /// 1. the length of the prefix that contains no percent-encoded triplets,
83 /// 1. the first decoded byte, and
84 /// 1. the suffix after the decoded percent-encoded triplet.
85 #[must_use]
86 fn try_non_allocating_decode(&self) -> Option<(usize, u8, &'a [u8])> {
87 let mut len_before_pct;
88 let mut rest = self.not_yet_decoded;
89
90 while !rest.is_empty() {
91 #[cfg(feature = "memchr")]
92 let pct_pos = memchr::memchr(b'%', rest)?;
93 #[cfg(not(feature = "memchr"))]
94 let pct_pos = rest.iter().position(|&b| b == b'%')?;
95
96 let after_pct;
97 (len_before_pct, after_pct) = (pct_pos, &rest[(pct_pos + 1)..]);
98
99 let decoded;
100 (decoded, rest) = strip_decode_xdigits2(after_pct);
101 if let Some(decoded) = decoded {
102 return Some((len_before_pct, decoded, rest));
103 }
104 rest = after_pct;
105 }
106
107 None
108 }
109
110 /// Returns the decoded bytes as a slice if no memory allocation is needed.
111 ///
112 /// # Examples
113 ///
114 /// ```
115 /// use iri_string::percent_encode::decode::decode_whatwg_bytes;
116 ///
117 /// let no_alloc = decode_whatwg_bytes(b"99% unsafe");
118 /// assert_eq!(no_alloc.to_bytes(), Some(&b"99% unsafe"[..]));
119 ///
120 /// let alloc_needed = decode_whatwg_bytes(b"hello%20world");
121 /// assert_eq!(alloc_needed.to_bytes(), None);
122 /// ```
123 #[inline]
124 #[must_use]
125 pub fn to_bytes(&self) -> Option<&'a [u8]> {
126 match self.try_non_allocating_decode() {
127 None => Some(self.not_yet_decoded),
128 _ => None,
129 }
130 }
131
132 /// Decodes the bytes, based on [`percent-decode`] algorithm of the WHATWG URL standard.
133 ///
134 /// # Examples
135 ///
136 /// ```
137 /// use iri_string::percent_encode::decode::decode_whatwg_bytes;
138 ///
139 /// let decoded = decode_whatwg_bytes(b"hello%20world");
140 /// assert_eq!(decoded.into_bytes(), &b"hello world"[..]);
141 /// ```
142 ///
143 /// [`percent-decode`]: https://url.spec.whatwg.org/#percent-decode
144 #[cfg(feature = "alloc")]
145 #[inline]
146 #[must_use]
147 pub fn into_bytes(&self) -> Cow<'a, [u8]> {
148 use crate::parser::str::find_split_hole;
149
150 let (mut result, mut rest) = match self.try_non_allocating_decode() {
151 Some((prefix_len, decoded, rest)) => {
152 let mut prefix = alloc::vec::Vec::from(&self.not_yet_decoded[..prefix_len]);
153 prefix.push(decoded);
154 (prefix, rest)
155 }
156 None => return Cow::Borrowed(self.not_yet_decoded),
157 };
158
159 while !rest.is_empty() {
160 let after_pct = if let Some((no_pct, after_pct)) = find_split_hole(rest, b'%') {
161 result.extend(no_pct);
162 after_pct
163 } else {
164 result.extend(core::mem::take(&mut rest));
165 break;
166 };
167
168 let decoded;
169 (decoded, rest) = strip_decode_xdigits2(after_pct);
170 result.extend(decoded);
171 }
172
173 Cow::Owned(result)
174 }
175
176 /// Decodes the bytes into a string, based on [`percent-decode`] algorithm
177 /// of the WHATWG URL standard.
178 ///
179 /// # Examples
180 ///
181 /// ```
182 /// use iri_string::percent_encode::decode::decode_whatwg_bytes;
183 ///
184 /// let decoded = decode_whatwg_bytes(b"hello%20world");
185 /// assert_eq!(
186 /// decoded.into_string(),
187 /// Ok("hello world".to_owned())
188 /// );
189 /// ```
190 ///
191 /// [`percent-decode`]: https://url.spec.whatwg.org/#percent-decode
192 #[cfg(feature = "alloc")]
193 #[inline]
194 pub fn into_string(&self) -> Result<String, FromUtf8Error> {
195 String::from_utf8(self.into_bytes().into_owned())
196 }
197
198 /// Returns an iterator of decoded fragments.
199 ///
200 /// # Examples
201 ///
202 /// ```
203 /// use iri_string::percent_encode::decode::{
204 /// decode_whatwg_bytes, DecodedFragment,
205 /// };
206 ///
207 /// let mut i = decode_whatwg_bytes(b"100% hello%20world")
208 /// .bytes_fragments();
209 ///
210 /// assert_eq!(i.next(), Some(DecodedFragment::Direct(b"100")));
211 /// assert_eq!(i.next(), Some(DecodedFragment::StrayPercent));
212 /// assert_eq!(i.next(), Some(DecodedFragment::Direct(b" hello")));
213 /// assert_eq!(i.next(), Some(DecodedFragment::DecodedByte(b' ')));
214 /// assert_eq!(i.next(), Some(DecodedFragment::Direct(b"world")));
215 /// assert_eq!(i.next(), None);
216 /// ```
217 #[inline]
218 #[must_use]
219 pub fn bytes_fragments(&self) -> PercentDecodedBytesFragments<'a> {
220 PercentDecodedBytesFragments {
221 rest: self.not_yet_decoded,
222 }
223 }
224}
225
226impl fmt::Debug for PercentDecodedWhatwgBytes<'_> {
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 let mut f = f.debug_struct("PercentDecodedWhatwgBytes");
229 // Print as a string if possible.
230 match core::str::from_utf8(self.not_yet_decoded) {
231 Ok(s) => f.field("not_yet_decoded", &s),
232 Err(_) => f.field("not_yet_decoded", &self.not_yet_decoded),
233 };
234 f.finish()
235 }
236}
237
238/// Fragments in a percent-decodable byte sequence.
239#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
240pub enum DecodedFragment<'a> {
241 /// Bytes without percent characters nor percent-encoded triplets.
242 Direct(&'a [u8]),
243 /// A decoded byte from a percent-encoded triplet.
244 DecodedByte(u8),
245 /// A percent character that does not form a valid percent-encoded triplet.
246 StrayPercent,
247}
248
249/// An iterator of fragments in a percent-decodable byte sequence.
250//
251// NOTE: Do not implement `Copy` since an iterator type with `Copy` is a foot-gun.
252#[derive(Clone)]
253pub struct PercentDecodedBytesFragments<'a> {
254 /// Remaining string to decode.
255 rest: &'a [u8],
256}
257
258impl fmt::Debug for PercentDecodedBytesFragments<'_> {
259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 let mut f = f.debug_struct("PercentDecodedBytesFragments");
261 // Print as a string if possible.
262 match core::str::from_utf8(self.rest) {
263 Ok(s) => f.field("rest", &s),
264 Err(_) => f.field("rest", &self.rest),
265 };
266 f.finish()
267 }
268}
269
270impl<'a> PercentDecodedBytesFragments<'a> {
271 /// Returns the remaining not-yet-decoded bytes.
272 ///
273 /// # Examples
274 ///
275 /// ```
276 /// use iri_string::percent_encode::decode::{
277 /// decode_whatwg_bytes, DecodedFragment,
278 /// };
279 ///
280 /// let mut i = decode_whatwg_bytes(b"hello%20world")
281 /// .bytes_fragments();
282 ///
283 /// assert_eq!(i.next(), Some(DecodedFragment::Direct(b"hello")));
284 /// assert_eq!(i.not_yet_decoded(), &b"%20world"[..]);
285 /// ```
286 #[inline]
287 #[must_use]
288 pub fn not_yet_decoded(&self) -> &'a [u8] {
289 self.rest
290 }
291}
292
293impl<'a> Iterator for PercentDecodedBytesFragments<'a> {
294 type Item = DecodedFragment<'a>;
295
296 fn next(&mut self) -> Option<Self::Item> {
297 let mut rest = self.rest;
298
299 match rest {
300 [] => None,
301 [b'%', after_pct @ ..] => {
302 let decoded;
303 (decoded, rest) = strip_decode_xdigits2(after_pct);
304 if let Some(decoded) = decoded {
305 self.rest = rest;
306 Some(DecodedFragment::DecodedByte(decoded))
307 } else {
308 self.rest = after_pct;
309 Some(DecodedFragment::StrayPercent)
310 }
311 }
312 [_, after_first @ ..] => {
313 #[cfg(feature = "memchr")]
314 let pct_pos_minus_one = memchr::memchr(b'%', after_first);
315 #[cfg(not(feature = "memchr"))]
316 let pct_pos_minus_one = after_first.iter().position(|&b| b == b'%');
317
318 let before_pct;
319 (before_pct, self.rest) = match pct_pos_minus_one {
320 None => (rest, &rest[rest.len()..]),
321 Some(pct_pos_minus_one) => rest.split_at(pct_pos_minus_one + 1),
322 };
323 Some(DecodedFragment::Direct(before_pct))
324 }
325 }
326 }
327}