1use crate::{
17 class,
18 error::{ErrorKind, Result, WriteError, err},
19 pct::{EncodeSet, HexCase},
20 scan::control_kind,
21 utf8::Utf8,
22 write::{Sink, reserve},
23};
24
25#[non_exhaustive]
37#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
38pub enum Charset {
39 Utf8,
41 Iso8859_1,
43 UsAscii,
45}
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
58pub struct ExtValue<'a> {
59 charset: Charset,
60 language: &'a [u8],
61 encoded: &'a [u8],
62}
63
64const fn is_mime_charset_char(b: u8) -> bool {
66 b.is_ascii_alphanumeric()
67 || matches!(
68 b,
69 b'!' | b'#'
70 | b'$'
71 | b'%'
72 | b'&'
73 | b'+'
74 | b'-'
75 | b'^'
76 | b'_'
77 | b'`'
78 | b'{'
79 | b'}'
80 | b'~'
81 )
82}
83
84const fn is_control_point(c: u32) -> bool {
86 c < 0x20 || (c >= 0x7f && c <= 0x9f)
87}
88
89impl<'a> ExtValue<'a> {
90 pub fn parse(value: &'a [u8]) -> Result<Self> {
114 let Some(q1) = value.iter().position(|&b| b == b'\'') else {
115 return err(ErrorKind::InvalidCharset, value.len());
116 };
117 let charset_text = value.get(..q1).unwrap_or(&[]);
118 if charset_text.is_empty() || !charset_text.iter().all(|&b| is_mime_charset_char(b)) {
119 return err(ErrorKind::InvalidCharset, 0);
120 }
121 let charset = if charset_text.eq_ignore_ascii_case(b"UTF-8") {
122 Charset::Utf8
123 } else if charset_text.eq_ignore_ascii_case(b"ISO-8859-1") {
124 Charset::Iso8859_1
125 } else if charset_text.eq_ignore_ascii_case(b"US-ASCII") {
126 Charset::UsAscii
127 } else {
128 return err(ErrorKind::UnsupportedCharset, 0);
129 };
130 let after = value.get(q1 + 1..).unwrap_or(&[]);
131 let Some(q2) = after.iter().position(|&b| b == b'\'') else {
132 return err(ErrorKind::InvalidCharset, value.len());
133 };
134 let language = after.get(..q2).unwrap_or(&[]);
135 if let Some(i) = language
136 .iter()
137 .position(|&b| !(b.is_ascii_alphanumeric() || b == b'-'))
138 {
139 return err(ErrorKind::InvalidCharset, q1 + 1 + i);
140 }
141 let encoded_at = q1 + 1 + q2 + 1;
142 let encoded = after.get(q2 + 1..).unwrap_or(&[]);
143 let mut i = 0usize;
144 while let Some(&b) = encoded.get(i) {
145 if b == b'%' {
146 match (encoded.get(i + 1), encoded.get(i + 2)) {
147 (Some(&h), Some(&l)) if class::is_hexdig(h) && class::is_hexdig(l) => i += 3,
148 _ => return err(ErrorKind::InvalidPercentEncoding, encoded_at + i),
149 }
150 } else if class::is_attr_char(b) {
151 i += 1;
152 } else if crate::scan::is_control(b) {
153 return err(control_kind(b), encoded_at + i);
154 } else {
155 return err(ErrorKind::InvalidByte, encoded_at + i);
156 }
157 }
158 Ok(Self {
159 charset,
160 language,
161 encoded,
162 })
163 }
164
165 #[must_use]
175 pub const fn charset(&self) -> Charset {
176 self.charset
177 }
178
179 #[must_use]
191 pub const fn language(&self) -> &'a [u8] {
192 self.language
193 }
194
195 #[must_use]
205 pub const fn encoded(&self) -> &'a [u8] {
206 self.encoded
207 }
208
209 pub(crate) fn raw_bytes(&self) -> PctBytes<'a> {
211 PctBytes {
212 rest: self.encoded.iter(),
213 }
214 }
215
216 #[must_use]
229 pub fn max_decoded_len(&self) -> usize {
230 let n = self.raw_bytes().count();
231 match self.charset {
232 Charset::Utf8 | Charset::UsAscii => n,
233 Charset::Iso8859_1 => n.saturating_mul(2),
234 }
235 }
236
237 pub(crate) fn check_text(&self) -> Result<()> {
240 decoded_text_len(self.raw_bytes(), self.charset).map(|_| ())
241 }
242
243 pub fn decode_into<'b>(&self, out: &'b mut [u8]) -> Result<&'b str> {
266 decode_checked(self.raw_bytes(), self.charset, out)
267 }
268}
269
270pub(crate) fn decode_checked(
273 bytes: impl Iterator<Item = u8> + Clone,
274 charset: Charset,
275 out: &mut [u8],
276) -> Result<&str> {
277 let len = decoded_text_len(bytes.clone(), charset)?;
278 let Some(dst) = out.get_mut(..len) else {
279 return err(ErrorKind::BufferTooSmall, 0);
280 };
281 let mut slots = dst.iter_mut();
282 for b in bytes {
283 match charset {
284 Charset::Utf8 | Charset::UsAscii => {
285 if let Some(s) = slots.next() {
286 *s = b;
287 }
288 }
289 Charset::Iso8859_1 => {
290 if b < 0x80 {
291 if let Some(s) = slots.next() {
292 *s = b;
293 }
294 } else {
295 if let Some(s) = slots.next() {
297 *s = 0xc0 | (b >> 6);
298 }
299 if let Some(s) = slots.next() {
300 *s = 0x80 | (b & 0x3f);
301 }
302 }
303 }
304 }
305 }
306 core::str::from_utf8(dst)
307 .map_err(|e| crate::ParseError::new(ErrorKind::InvalidUtf8, e.valid_up_to()))
308}
309
310pub(crate) fn decoded_text_len(bytes: impl Iterator<Item = u8>, charset: Charset) -> Result<usize> {
313 let mut len = 0usize;
314 match charset {
315 Charset::Utf8 => {
316 let mut v = Utf8::new();
317 let mut point = 0u32;
318 for (i, b) in bytes.enumerate() {
319 if !v.push(b) {
320 return err(ErrorKind::InvalidUtf8, i);
321 }
322 point = match b {
325 0x00..=0x7f => u32::from(b),
326 0x80..=0xbf => (point << 6) | u32::from(b & 0x3f),
327 0xc0..=0xdf => u32::from(b & 0x1f),
328 0xe0..=0xef => u32::from(b & 0x0f),
329 _ => u32::from(b & 0x07),
330 };
331 if v.is_complete() && is_control_point(point) {
332 return err(ErrorKind::ControlInDecoded, i);
333 }
334 len += 1;
335 }
336 if !v.is_complete() {
337 return err(ErrorKind::InvalidUtf8, len);
338 }
339 }
340 Charset::Iso8859_1 => {
341 for (i, b) in bytes.enumerate() {
342 if is_control_point(u32::from(b)) {
343 return err(ErrorKind::ControlInDecoded, i);
344 }
345 len += if b < 0x80 { 1 } else { 2 };
346 }
347 }
348 Charset::UsAscii => {
349 for (i, b) in bytes.enumerate() {
350 if b >= 0x80 {
351 return err(ErrorKind::InvalidByte, i);
352 }
353 if is_control_point(u32::from(b)) {
354 return err(ErrorKind::ControlInDecoded, i);
355 }
356 len += 1;
357 }
358 }
359 }
360 Ok(len)
361}
362
363#[derive(Clone)]
365pub(crate) struct PctBytes<'a> {
366 pub(crate) rest: core::slice::Iter<'a, u8>,
367}
368
369impl Iterator for PctBytes<'_> {
370 type Item = u8;
371
372 fn next(&mut self) -> Option<u8> {
373 let b = *self.rest.next()?;
374 if b != b'%' {
375 return Some(b);
376 }
377 let hi = self.rest.next().copied().and_then(class::hex_value);
378 let lo = self.rest.next().copied().and_then(class::hex_value);
379 match (hi, lo) {
380 (Some(h), Some(l)) => Some((h << 4) | l),
381 _ => Some(b),
382 }
383 }
384}
385
386pub fn write_ext_value(text: &str, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
405 for (i, c) in text.char_indices() {
406 if is_control_point(u32::from(c)) {
407 return Err(WriteError::forbidden(i));
408 }
409 }
410 let set = EncodeSet::ATTR_CHAR_COMPLEMENT;
411 reserve(
412 out,
413 7usize.saturating_add(crate::pct::encoded_len(text.as_bytes(), &set)),
414 )?;
415 out.put(b"UTF-8''")?;
416 crate::pct::encode(text.as_bytes(), &set, HexCase::Upper, out)
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422
423 fn decode(v: &[u8]) -> Result<std::string::String> {
424 let e = ExtValue::parse(v)?;
425 let mut buf = [0u8; 64];
426 e.decode_into(&mut buf).map(std::string::String::from)
427 }
428
429 #[test]
430 fn test_ext_value_charset_matrix() {
431 assert_eq!(decode(b"UTF-8''").unwrap(), "");
432 assert_eq!(decode(b"utf-8''a").unwrap(), "a");
433 assert_eq!(decode(b"UTF-8'en'a").unwrap(), "a");
434 assert_eq!(decode(b"ISO-8859-1''%E9").unwrap(), "\u{e9}");
435 assert_eq!(decode(b"iso-8859-1''caf%e9").unwrap(), "caf\u{e9}");
436 assert_eq!(decode(b"us-ascii'en'a%20b").unwrap(), "a b");
437 assert_eq!(
438 decode(b"US-ASCII''caf%e9").unwrap_err().kind(),
439 ErrorKind::InvalidByte
440 );
441 assert_eq!(decode(b"UTF-8''%C3%A9").unwrap(), "\u{e9}");
442 assert_eq!(
444 decode(b"UTF-8''%E9").unwrap_err().kind(),
445 ErrorKind::InvalidUtf8
446 );
447 assert_eq!(decode(b"ISO-8859-1''%C3%A9").unwrap(), "\u{c3}\u{a9}");
448 assert_eq!(
449 ExtValue::parse(b"UTF-16''a").unwrap_err().kind(),
450 ErrorKind::UnsupportedCharset
451 );
452 assert_eq!(
453 ExtValue::parse(b"''a").unwrap_err().kind(),
454 ErrorKind::InvalidCharset
455 );
456 assert_eq!(
457 ExtValue::parse(b"UTF-8'a").unwrap_err().kind(),
458 ErrorKind::InvalidCharset
459 );
460 assert_eq!(
461 ExtValue::parse(b"UTF 8''a").unwrap_err().kind(),
462 ErrorKind::InvalidCharset
463 );
464 assert_eq!(
465 ExtValue::parse(b"UTF-8'e n'a").unwrap_err().kind(),
466 ErrorKind::InvalidCharset
467 );
468 }
469
470 #[test]
471 fn test_ext_value_percent_crlf_refused() {
472 assert_eq!(
473 ExtValue::parse(b"UTF-8''%").unwrap_err().kind(),
474 ErrorKind::InvalidPercentEncoding
475 );
476 assert_eq!(
477 ExtValue::parse(b"UTF-8''%G0").unwrap_err().kind(),
478 ErrorKind::InvalidPercentEncoding
479 );
480 assert_eq!(
481 decode(b"UTF-8''%C3").unwrap_err().kind(),
482 ErrorKind::InvalidUtf8
483 );
484 assert_eq!(
485 decode(b"UTF-8''%C0%AF").unwrap_err().kind(),
486 ErrorKind::InvalidUtf8
487 );
488 assert_eq!(
489 decode(b"UTF-8''%00").unwrap_err().kind(),
490 ErrorKind::ControlInDecoded
491 );
492 assert_eq!(
493 decode(b"UTF-8''a%0D%0Ab").unwrap_err().kind(),
494 ErrorKind::ControlInDecoded
495 );
496 assert_eq!(
497 decode(b"UTF-8''%C2%85").unwrap_err().kind(),
498 ErrorKind::ControlInDecoded
499 );
500 assert_eq!(
501 decode(b"ISO-8859-1''%85").unwrap_err().kind(),
502 ErrorKind::ControlInDecoded
503 );
504 assert_eq!(decode(b"UTF-8''a%2Fb").unwrap(), "a/b");
505 assert_eq!(
506 ExtValue::parse(b"UTF-8''a b").unwrap_err().kind(),
507 ErrorKind::InvalidByte
508 );
509 assert_eq!(
510 ExtValue::parse(b"UTF-8''a\rb").unwrap_err().kind(),
511 ErrorKind::BareCr
512 );
513 }
514
515 #[test]
516 fn test_ext_value_buffer_and_lengths() {
517 let e = ExtValue::parse(b"ISO-8859-1''%E9%E9").unwrap();
518 assert_eq!(e.max_decoded_len(), 4);
519 let mut short = [0xaau8; 3];
520 assert_eq!(
521 e.decode_into(&mut short).unwrap_err().kind(),
522 ErrorKind::BufferTooSmall
523 );
524 assert_eq!(short, [0xaa; 3]);
525 let u = ExtValue::parse(b"UTF-8''%E2%82%AC").unwrap();
526 assert_eq!(u.max_decoded_len(), 3);
527 }
528
529 #[test]
530 fn test_write_ext_value_round_trip_and_refusals() {
531 let mut buf = [0u8; 128];
532 let mut out = crate::SliceSink::new(&mut buf);
533 assert_eq!(
534 write_ext_value("a\r\nb", &mut out),
535 Err(WriteError::Forbidden { at: 1 })
536 );
537 assert_eq!(
538 write_ext_value("x\u{85}", &mut out),
539 Err(WriteError::Forbidden { at: 1 })
540 );
541 write_ext_value("caf\u{e9} \u{1f600}/", &mut out).unwrap();
542 let back = ExtValue::parse(out.written()).unwrap();
543 let mut dec = [0u8; 64];
544 assert_eq!(back.decode_into(&mut dec).unwrap(), "caf\u{e9} \u{1f600}/");
545 }
546}