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
84pub(crate) fn is_mime_attribute_char(b: u8) -> bool {
86 class::is_mime_token_char(b) && !matches!(b, b'*' | b'\'' | b'%')
87}
88
89const fn is_control_point(c: u32) -> bool {
91 c < 0x20 || (c >= 0x7f && c <= 0x9f)
92}
93
94impl<'a> ExtValue<'a> {
95 pub fn parse(value: &'a [u8]) -> Result<Self> {
119 Self::parse_with(value, class::is_attr_char)
120 }
121
122 pub(crate) fn parse_mime(value: &'a [u8]) -> Result<Self> {
125 Self::parse_with(value, is_mime_attribute_char)
126 }
127
128 fn parse_with(value: &'a [u8], in_value: fn(u8) -> bool) -> Result<Self> {
129 let (head, encoded_at) = Self::head(value)?;
130 let mut i = 0usize;
131 while let Some(&b) = head.encoded.get(i) {
132 if b == b'%' {
133 match (head.encoded.get(i + 1), head.encoded.get(i + 2)) {
134 (Some(&h), Some(&l)) if class::is_hexdig(h) && class::is_hexdig(l) => i += 3,
135 _ => return err(ErrorKind::InvalidPercentEncoding, encoded_at + i),
136 }
137 } else if in_value(b) {
138 i += 1;
139 } else if crate::scan::is_control(b) {
140 return err(control_kind(b), encoded_at + i);
141 } else {
142 return err(ErrorKind::InvalidByte, encoded_at + i);
143 }
144 }
145 Ok(head)
146 }
147
148 pub(crate) fn from_validated(value: &'a [u8]) -> Option<Self> {
151 Self::head(value).ok().map(|(head, _)| head)
152 }
153
154 fn head(value: &'a [u8]) -> Result<(Self, usize)> {
157 let Some(q1) = value.iter().position(|&b| b == b'\'') else {
158 return err(ErrorKind::InvalidCharset, value.len());
159 };
160 let charset_text = value.get(..q1).unwrap_or(&[]);
161 if charset_text.is_empty() || !charset_text.iter().all(|&b| is_mime_charset_char(b)) {
162 return err(ErrorKind::InvalidCharset, 0);
163 }
164 let charset = if charset_text.eq_ignore_ascii_case(b"UTF-8") {
165 Charset::Utf8
166 } else if charset_text.eq_ignore_ascii_case(b"ISO-8859-1") {
167 Charset::Iso8859_1
168 } else if charset_text.eq_ignore_ascii_case(b"US-ASCII") {
169 Charset::UsAscii
170 } else {
171 return err(ErrorKind::UnsupportedCharset, 0);
172 };
173 let after = value.get(q1 + 1..).unwrap_or(&[]);
174 let Some(q2) = after.iter().position(|&b| b == b'\'') else {
175 return err(ErrorKind::InvalidCharset, value.len());
176 };
177 let language = after.get(..q2).unwrap_or(&[]);
178 if let Some(i) = language
179 .iter()
180 .position(|&b| !(b.is_ascii_alphanumeric() || b == b'-'))
181 {
182 return err(ErrorKind::InvalidCharset, q1 + 1 + i);
183 }
184 let encoded = after.get(q2 + 1..).unwrap_or(&[]);
185 Ok((
186 Self {
187 charset,
188 language,
189 encoded,
190 },
191 q1 + 1 + q2 + 1,
192 ))
193 }
194
195 #[must_use]
205 pub const fn charset(&self) -> Charset {
206 self.charset
207 }
208
209 #[must_use]
221 pub const fn language(&self) -> &'a [u8] {
222 self.language
223 }
224
225 #[must_use]
235 pub const fn encoded(&self) -> &'a [u8] {
236 self.encoded
237 }
238
239 pub(crate) fn raw_bytes(&self) -> PctBytes<'a> {
241 PctBytes {
242 rest: self.encoded.iter(),
243 }
244 }
245
246 #[must_use]
259 pub fn max_decoded_len(&self) -> usize {
260 let n = self.raw_bytes().count();
261 match self.charset {
262 Charset::Utf8 | Charset::UsAscii => n,
263 Charset::Iso8859_1 => n.saturating_mul(2),
264 }
265 }
266
267 pub(crate) fn check_text(&self) -> Result<()> {
270 decoded_text_len(self.raw_bytes(), self.charset).map(|_| ())
271 }
272
273 pub fn decode_into<'b>(&self, out: &'b mut [u8]) -> Result<&'b str> {
296 decode_checked(self.raw_bytes(), self.charset, out)
297 }
298}
299
300pub(crate) fn decode_checked(
303 bytes: impl Iterator<Item = u8> + Clone,
304 charset: Charset,
305 out: &mut [u8],
306) -> Result<&str> {
307 let len = decoded_text_len(bytes.clone(), charset)?;
308 let Some(dst) = out.get_mut(..len) else {
309 return err(ErrorKind::BufferTooSmall, 0);
310 };
311 let mut slots = dst.iter_mut();
312 for b in bytes {
313 match charset {
314 Charset::Utf8 | Charset::UsAscii => {
315 if let Some(s) = slots.next() {
316 *s = b;
317 }
318 }
319 Charset::Iso8859_1 => {
320 if b < 0x80 {
321 if let Some(s) = slots.next() {
322 *s = b;
323 }
324 } else {
325 if let Some(s) = slots.next() {
327 *s = 0xc0 | (b >> 6);
328 }
329 if let Some(s) = slots.next() {
330 *s = 0x80 | (b & 0x3f);
331 }
332 }
333 }
334 }
335 }
336 core::str::from_utf8(dst)
337 .map_err(|e| crate::ParseError::new(ErrorKind::InvalidUtf8, e.valid_up_to()))
338}
339
340pub(crate) fn decoded_text_len(bytes: impl Iterator<Item = u8>, charset: Charset) -> Result<usize> {
343 let mut len = 0usize;
344 match charset {
345 Charset::Utf8 => {
346 let mut v = Utf8::new();
347 let mut point = 0u32;
348 for (i, b) in bytes.enumerate() {
349 if !v.push(b) {
350 return err(ErrorKind::InvalidUtf8, i);
351 }
352 point = match b {
355 0x00..=0x7f => u32::from(b),
356 0x80..=0xbf => (point << 6) | u32::from(b & 0x3f),
357 0xc0..=0xdf => u32::from(b & 0x1f),
358 0xe0..=0xef => u32::from(b & 0x0f),
359 _ => u32::from(b & 0x07),
360 };
361 if v.is_complete() && is_control_point(point) {
362 return err(ErrorKind::ControlInDecoded, i);
363 }
364 len += 1;
365 }
366 if !v.is_complete() {
367 return err(ErrorKind::InvalidUtf8, len);
368 }
369 }
370 Charset::Iso8859_1 => {
371 for (i, b) in bytes.enumerate() {
372 if is_control_point(u32::from(b)) {
373 return err(ErrorKind::ControlInDecoded, i);
374 }
375 len += if b < 0x80 { 1 } else { 2 };
376 }
377 }
378 Charset::UsAscii => {
379 for (i, b) in bytes.enumerate() {
380 if b >= 0x80 {
381 return err(ErrorKind::InvalidByte, i);
382 }
383 if is_control_point(u32::from(b)) {
384 return err(ErrorKind::ControlInDecoded, i);
385 }
386 len += 1;
387 }
388 }
389 }
390 Ok(len)
391}
392
393#[derive(Clone)]
395pub(crate) struct PctBytes<'a> {
396 pub(crate) rest: core::slice::Iter<'a, u8>,
397}
398
399impl Iterator for PctBytes<'_> {
400 type Item = u8;
401
402 fn next(&mut self) -> Option<u8> {
403 let b = *self.rest.next()?;
404 if b != b'%' {
405 return Some(b);
406 }
407 let hi = self.rest.next().copied().and_then(class::hex_value);
408 let lo = self.rest.next().copied().and_then(class::hex_value);
409 match (hi, lo) {
410 (Some(h), Some(l)) => Some((h << 4) | l),
411 _ => Some(b),
412 }
413 }
414}
415
416pub fn write_ext_value(text: &str, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
435 for (i, c) in text.char_indices() {
436 if is_control_point(u32::from(c)) {
437 return Err(WriteError::forbidden(i));
438 }
439 }
440 let set = EncodeSet::ATTR_CHAR_COMPLEMENT;
441 reserve(
442 out,
443 7usize.saturating_add(crate::pct::encoded_len(text.as_bytes(), &set)),
444 )?;
445 out.put(b"UTF-8''")?;
446 crate::pct::encode(text.as_bytes(), &set, HexCase::Upper, out)
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452
453 fn decode(v: &[u8]) -> Result<std::string::String> {
454 let e = ExtValue::parse(v)?;
455 let mut buf = [0u8; 64];
456 e.decode_into(&mut buf).map(std::string::String::from)
457 }
458
459 #[test]
460 fn test_ext_value_charset_matrix() {
461 assert_eq!(decode(b"UTF-8''").unwrap(), "");
462 assert_eq!(decode(b"utf-8''a").unwrap(), "a");
463 assert_eq!(decode(b"UTF-8'en'a").unwrap(), "a");
464 assert_eq!(decode(b"ISO-8859-1''%E9").unwrap(), "\u{e9}");
465 assert_eq!(decode(b"iso-8859-1''caf%e9").unwrap(), "caf\u{e9}");
466 assert_eq!(decode(b"us-ascii'en'a%20b").unwrap(), "a b");
467 assert_eq!(
468 decode(b"US-ASCII''caf%e9").unwrap_err().kind(),
469 ErrorKind::InvalidByte
470 );
471 assert_eq!(decode(b"UTF-8''%C3%A9").unwrap(), "\u{e9}");
472 assert_eq!(
474 decode(b"UTF-8''%E9").unwrap_err().kind(),
475 ErrorKind::InvalidUtf8
476 );
477 assert_eq!(decode(b"ISO-8859-1''%C3%A9").unwrap(), "\u{c3}\u{a9}");
478 assert_eq!(
479 ExtValue::parse(b"UTF-16''a").unwrap_err().kind(),
480 ErrorKind::UnsupportedCharset
481 );
482 assert_eq!(
483 ExtValue::parse(b"''a").unwrap_err().kind(),
484 ErrorKind::InvalidCharset
485 );
486 assert_eq!(
487 ExtValue::parse(b"UTF-8'a").unwrap_err().kind(),
488 ErrorKind::InvalidCharset
489 );
490 assert_eq!(
491 ExtValue::parse(b"UTF 8''a").unwrap_err().kind(),
492 ErrorKind::InvalidCharset
493 );
494 assert_eq!(
495 ExtValue::parse(b"UTF-8'e n'a").unwrap_err().kind(),
496 ErrorKind::InvalidCharset
497 );
498 }
499
500 #[test]
501 fn test_ext_value_percent_crlf_refused() {
502 assert_eq!(
503 ExtValue::parse(b"UTF-8''%").unwrap_err().kind(),
504 ErrorKind::InvalidPercentEncoding
505 );
506 assert_eq!(
507 ExtValue::parse(b"UTF-8''%G0").unwrap_err().kind(),
508 ErrorKind::InvalidPercentEncoding
509 );
510 assert_eq!(
511 decode(b"UTF-8''%C3").unwrap_err().kind(),
512 ErrorKind::InvalidUtf8
513 );
514 assert_eq!(
515 decode(b"UTF-8''%C0%AF").unwrap_err().kind(),
516 ErrorKind::InvalidUtf8
517 );
518 assert_eq!(
519 decode(b"UTF-8''%00").unwrap_err().kind(),
520 ErrorKind::ControlInDecoded
521 );
522 assert_eq!(
523 decode(b"UTF-8''a%0D%0Ab").unwrap_err().kind(),
524 ErrorKind::ControlInDecoded
525 );
526 assert_eq!(
527 decode(b"UTF-8''%C2%85").unwrap_err().kind(),
528 ErrorKind::ControlInDecoded
529 );
530 assert_eq!(
531 decode(b"ISO-8859-1''%85").unwrap_err().kind(),
532 ErrorKind::ControlInDecoded
533 );
534 assert_eq!(decode(b"UTF-8''a%2Fb").unwrap(), "a/b");
535 assert_eq!(
536 ExtValue::parse(b"UTF-8''a b").unwrap_err().kind(),
537 ErrorKind::InvalidByte
538 );
539 assert_eq!(
540 ExtValue::parse(b"UTF-8''a\rb").unwrap_err().kind(),
541 ErrorKind::BareCr
542 );
543 }
544
545 #[test]
546 fn test_ext_value_buffer_and_lengths() {
547 let e = ExtValue::parse(b"ISO-8859-1''%E9%E9").unwrap();
548 assert_eq!(e.max_decoded_len(), 4);
549 let mut short = [0xaau8; 3];
550 assert_eq!(
551 e.decode_into(&mut short).unwrap_err().kind(),
552 ErrorKind::BufferTooSmall
553 );
554 assert_eq!(short, [0xaa; 3]);
555 let u = ExtValue::parse(b"UTF-8''%E2%82%AC").unwrap();
556 assert_eq!(u.max_decoded_len(), 3);
557 }
558
559 #[test]
560 fn test_write_ext_value_round_trip_and_refusals() {
561 let mut buf = [0u8; 128];
562 let mut out = crate::SliceSink::new(&mut buf);
563 assert_eq!(
564 write_ext_value("a\r\nb", &mut out),
565 Err(WriteError::Forbidden { at: 1 })
566 );
567 assert_eq!(
568 write_ext_value("x\u{85}", &mut out),
569 Err(WriteError::Forbidden { at: 1 })
570 );
571 write_ext_value("caf\u{e9} \u{1f600}/", &mut out).unwrap();
572 let back = ExtValue::parse(out.written()).unwrap();
573 let mut dec = [0u8; 64];
574 assert_eq!(back.decode_into(&mut dec).unwrap(), "caf\u{e9} \u{1f600}/");
575 }
576}