1use crate::{
26 class::{self, Class},
27 error::{ErrorKind, Result, WriteError, err},
28 scan::control_kind,
29 utf8::Utf8,
30 write::{Sink, reserve},
31};
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
47pub struct EncodeSet([u64; 4]);
48
49#[derive(Clone, Copy)]
51enum Named {
52 ControlsAndNonAscii,
53 AttrCharComplement,
54 SfDisplay,
55}
56
57const fn in_named(b: u8, which: Named) -> bool {
58 match which {
59 Named::ControlsAndNonAscii => b < 0x20 || b >= 0x7f,
60 Named::AttrCharComplement => !class::is_attr_char(b),
61 Named::SfDisplay => b == b'%' || b == b'"' || b < 0x20 || b > 0x7e,
62 }
63}
64
65const fn set_of(which: Named) -> [u64; 4] {
66 let mut bits = [0u64; 4];
67 let mut b: u8 = 0;
68 loop {
69 if in_named(b, which) {
70 let word = (b >> 6) as usize;
71 #[allow(clippy::indexing_slicing)]
73 {
74 bits[word] |= 1 << (b & 63);
75 }
76 }
77 if b == u8::MAX {
78 break;
79 }
80 b += 1;
81 }
82 bits
83}
84
85impl EncodeSet {
86 pub const CONTROLS_AND_NON_ASCII: Self = Self(set_of(Named::ControlsAndNonAscii));
88 pub const ATTR_CHAR_COMPLEMENT: Self = Self(set_of(Named::AttrCharComplement));
90 pub const SF_DISPLAY: Self = Self(set_of(Named::SfDisplay));
92
93 #[must_use]
95 pub const fn empty() -> Self {
96 Self([0; 4])
97 }
98
99 #[must_use]
101 pub const fn add(self, b: u8) -> Self {
102 let mut bits = self.0;
103 let word = (b >> 6) as usize;
104 #[allow(clippy::indexing_slicing)]
106 {
107 bits[word] |= 1 << (b & 63);
108 }
109 Self(bits)
110 }
111
112 #[must_use]
114 pub const fn remove(self, b: u8) -> Self {
115 let mut bits = self.0;
116 let word = (b >> 6) as usize;
117 #[allow(clippy::indexing_slicing)]
119 {
120 bits[word] &= !(1 << (b & 63));
121 }
122 Self(bits)
123 }
124
125 #[must_use]
127 pub const fn contains(&self, b: u8) -> bool {
128 let word = (b >> 6) as usize;
129 #[allow(clippy::indexing_slicing)]
131 let w = self.0[word];
132 w & (1 << (b & 63)) != 0
133 }
134}
135
136#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
138pub enum HexCase {
139 Upper,
141 Lower,
143}
144
145#[derive(Clone, Copy, PartialEq, Eq)]
147enum Accept {
148 AnyCase,
149 LowerOnly,
150}
151
152#[inline]
154fn walk(input: &[u8], accept: Accept, mut f: impl FnMut(u8, usize) -> Result<()>) -> Result<()> {
155 let mut i = 0usize;
156 while let Some(&b) = input.get(i) {
157 if b == b'%' {
158 let hi = input.get(i + 1).copied().and_then(|h| hex(h, accept));
159 let lo = input.get(i + 2).copied().and_then(|h| hex(h, accept));
160 let (Some(hi), Some(lo)) = (hi, lo) else {
161 return err(ErrorKind::InvalidPercentEncoding, i);
162 };
163 f((hi << 4) | lo, i)?;
164 i += 3;
165 } else {
166 f(b, i)?;
167 i += 1;
168 }
169 }
170 Ok(())
171}
172
173#[inline]
174const fn hex(b: u8, accept: Accept) -> Option<u8> {
175 match (b, accept) {
176 (b'A'..=b'F', Accept::LowerOnly) => None,
177 _ => class::hex_value(b),
178 }
179}
180
181pub fn validate(input: &[u8]) -> Result<()> {
196 walk(input, Accept::AnyCase, |_, _| Ok(()))
197}
198
199pub fn validate_with(input: &[u8], allowed: Class) -> Result<()> {
218 let mut i = 0usize;
219 while let Some(&b) = input.get(i) {
220 if b == b'%' {
221 match (input.get(i + 1), input.get(i + 2)) {
222 (Some(&h), Some(&l)) if class::is_hexdig(h) && class::is_hexdig(l) => i += 3,
223 _ => return err(ErrorKind::InvalidPercentEncoding, i),
224 }
225 } else if allowed.contains(b) {
226 i += 1;
227 } else if crate::scan::is_control(b) {
228 return err(control_kind(b), i);
229 } else {
230 return err(ErrorKind::InvalidByte, i);
231 }
232 }
233 Ok(())
234}
235
236pub fn decoded_len(input: &[u8]) -> Result<usize> {
249 let mut n = 0usize;
250 walk(input, Accept::AnyCase, |_, _| {
251 n += 1;
252 Ok(())
253 })?;
254 Ok(n)
255}
256
257fn decode_with<'b>(
258 input: &[u8],
259 out: &'b mut [u8],
260 accept: Accept,
261 utf8: bool,
262) -> Result<&'b [u8]> {
263 let mut n = 0usize;
265 let mut v = Utf8::new();
266 walk(input, accept, |b, at| {
267 if utf8 && !v.push(b) {
268 return err(ErrorKind::InvalidUtf8, at);
269 }
270 n += 1;
271 Ok(())
272 })?;
273 if utf8 && !v.is_complete() {
274 return err(ErrorKind::InvalidUtf8, input.len());
275 }
276 let Some(dst) = out.get_mut(..n) else {
277 return err(ErrorKind::BufferTooSmall, 0);
278 };
279 let mut slots = dst.iter_mut();
280 walk(input, accept, |b, _| {
281 if let Some(slot) = slots.next() {
282 *slot = b;
283 }
284 Ok(())
285 })?;
286 Ok(dst)
287}
288
289pub fn decode_into<'b>(input: &[u8], out: &'b mut [u8]) -> Result<&'b [u8]> {
305 decode_with(input, out, Accept::AnyCase, false)
306}
307
308pub fn decode_utf8_into<'b>(input: &[u8], out: &'b mut [u8]) -> Result<&'b str> {
327 let bytes = decode_with(input, out, Accept::AnyCase, true)?;
328 core::str::from_utf8(bytes)
329 .map_err(|e| crate::ParseError::new(ErrorKind::InvalidUtf8, e.valid_up_to()))
330}
331
332pub fn decode_lower_only_into<'b>(input: &[u8], out: &'b mut [u8]) -> Result<&'b [u8]> {
350 decode_with(input, out, Accept::LowerOnly, false)
351}
352
353#[inline]
354fn must_encode(b: u8, set: &EncodeSet) -> bool {
355 b >= 0x80 || set.contains(b)
356}
357
358#[must_use]
368pub fn encoded_len(input: &[u8], set: &EncodeSet) -> usize {
369 input
370 .iter()
371 .map(|&b| if must_encode(b, set) { 3 } else { 1 })
372 .fold(0usize, usize::saturating_add)
373}
374
375pub fn encode(
393 input: &[u8],
394 set: &EncodeSet,
395 case: HexCase,
396 out: &mut impl Sink,
397) -> core::result::Result<(), WriteError> {
398 reserve(out, encoded_len(input, set))?;
399 let digits: &[u8; 16] = match case {
400 HexCase::Upper => b"0123456789ABCDEF",
401 HexCase::Lower => b"0123456789abcdef",
402 };
403 let mut rest = input;
404 while !rest.is_empty() {
405 let plain = rest.iter().take_while(|&&b| !must_encode(b, set)).count();
406 let (literal, tail) = rest.split_at(plain);
407 out.put(literal)?;
408 if let Some((&b, after)) = tail.split_first() {
409 let hi = digits.get(usize::from(b >> 4)).copied().unwrap_or(b'0');
410 let lo = digits.get(usize::from(b & 15)).copied().unwrap_or(b'0');
411 out.put(&[b'%', hi, lo])?;
412 rest = after;
413 } else {
414 rest = &[];
415 }
416 }
417 Ok(())
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423 use crate::write::SliceSink;
424
425 #[test]
426 fn test_pct_edge_cases() {
427 let mut buf = [0u8; 8];
428 for bad in [&b"%"[..], b"%4", b"%4g", b"a%", b"%g4"] {
429 assert_eq!(
430 validate(bad).unwrap_err().kind(),
431 ErrorKind::InvalidPercentEncoding
432 );
433 assert_eq!(
434 decode_into(bad, &mut buf).unwrap_err().kind(),
435 ErrorKind::InvalidPercentEncoding
436 );
437 }
438 assert_eq!(decode_into(b"%41", &mut buf).unwrap(), b"A");
439 assert_eq!(decode_into(b"%c3%a9", &mut buf).unwrap(), b"\xc3\xa9");
440 assert_eq!(decode_into(b"%C3%A9", &mut buf).unwrap(), b"\xc3\xa9");
441 assert_eq!(decode_into(b"%00", &mut buf).unwrap(), b"\x00");
442 assert_eq!(
443 decode_utf8_into(b"%c3", &mut buf).unwrap_err().kind(),
444 ErrorKind::InvalidUtf8
445 );
446 assert_eq!(
447 decode_utf8_into(b"%ed%a0%80", &mut buf).unwrap_err().kind(),
448 ErrorKind::InvalidUtf8
449 );
450 let e = decode_lower_only_into(b"ab%2F", &mut buf).unwrap_err();
451 assert_eq!(
452 (e.kind(), e.offset()),
453 (ErrorKind::InvalidPercentEncoding, 2)
454 );
455 }
456
457 #[test]
458 fn test_pct_decode_buffer_one_short_writes_nothing() {
459 let mut buf = [0xaau8; 2];
460 assert_eq!(
461 decode_into(b"a%20b", &mut buf).unwrap_err().kind(),
462 ErrorKind::BufferTooSmall
463 );
464 assert_eq!(buf, [0xaa, 0xaa]);
465 let mut buf = [0xaau8; 3];
466 assert_eq!(
467 decode_utf8_into(b"a%c3", &mut buf).unwrap_err().kind(),
468 ErrorKind::InvalidUtf8
469 );
470 assert_eq!(buf, [0xaa; 3]);
471 }
472
473 #[test]
474 fn test_validate_with_target_matches_http_net() {
475 assert!(validate_with(b"/a/b?c=d&e=%20", Class::URI_TARGET).is_ok());
476 assert!(validate_with(b"/a%2", Class::URI_TARGET).is_err());
477 assert!(validate_with(b"/a%zz", Class::URI_TARGET).is_err());
478 assert!(validate_with(b"/a b", Class::URI_TARGET).is_err());
479 assert!(validate_with(b"/a#frag", Class::URI_TARGET).is_err());
480 assert_eq!(
481 validate_with(b"/a\x7f", Class::URI_TARGET)
482 .unwrap_err()
483 .kind(),
484 ErrorKind::Control
485 );
486 }
487
488 #[test]
489 fn test_encode_sets_and_round_trip() {
490 for b in 0u8..=255 {
491 assert_eq!(
492 EncodeSet::ATTR_CHAR_COMPLEMENT.contains(b),
493 !class::is_attr_char(b)
494 );
495 assert_eq!(
496 EncodeSet::CONTROLS_AND_NON_ASCII.contains(b),
497 !(0x20..0x7f).contains(&b)
498 );
499 assert!(EncodeSet::empty().add(b).contains(b));
500 assert!(!EncodeSet::SF_DISPLAY.remove(b).contains(b));
501 }
502 let all: std::vec::Vec<u8> = (0u8..=255).collect();
503 let mut buf = [0u8; 1024];
504 let mut out = SliceSink::new(&mut buf);
505 encode(
506 &all,
507 &EncodeSet::ATTR_CHAR_COMPLEMENT,
508 HexCase::Upper,
509 &mut out,
510 )
511 .unwrap();
512 assert_eq!(
513 out.len(),
514 encoded_len(&all, &EncodeSet::ATTR_CHAR_COMPLEMENT)
515 );
516 let mut back = [0u8; 256];
517 assert_eq!(decode_into(out.written(), &mut back).unwrap(), &all[..]);
518 }
519
520 #[test]
521 fn test_encode_full_writes_nothing() {
522 let mut buf = [0u8; 4];
523 let mut out = SliceSink::new(&mut buf);
524 assert_eq!(
525 encode(
526 b"a b",
527 &EncodeSet::empty().add(b' '),
528 HexCase::Upper,
529 &mut out
530 ),
531 Err(WriteError::Full)
532 );
533 assert!(out.is_empty());
534 }
535}