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]
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28pub enum Charset {
29 Utf8,
31 Iso8859_1,
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
37pub struct ExtValue<'a> {
38 charset: Charset,
39 language: &'a [u8],
40 encoded: &'a [u8],
41}
42
43const fn is_mime_charset_char(b: u8) -> bool {
45 b.is_ascii_alphanumeric()
46 || matches!(
47 b,
48 b'!' | b'#'
49 | b'$'
50 | b'%'
51 | b'&'
52 | b'+'
53 | b'-'
54 | b'^'
55 | b'_'
56 | b'`'
57 | b'{'
58 | b'}'
59 | b'~'
60 )
61}
62
63const fn is_control_point(c: u32) -> bool {
65 c < 0x20 || (c >= 0x7f && c <= 0x9f)
66}
67
68impl<'a> ExtValue<'a> {
69 pub fn parse(value: &'a [u8]) -> Result<Self> {
82 let Some(q1) = value.iter().position(|&b| b == b'\'') else {
83 return err(ErrorKind::InvalidCharset, value.len());
84 };
85 let charset_text = value.get(..q1).unwrap_or(&[]);
86 if charset_text.is_empty() || !charset_text.iter().all(|&b| is_mime_charset_char(b)) {
87 return err(ErrorKind::InvalidCharset, 0);
88 }
89 let charset = if charset_text.eq_ignore_ascii_case(b"UTF-8") {
90 Charset::Utf8
91 } else if charset_text.eq_ignore_ascii_case(b"ISO-8859-1") {
92 Charset::Iso8859_1
93 } else {
94 return err(ErrorKind::UnsupportedCharset, 0);
95 };
96 let after = value.get(q1 + 1..).unwrap_or(&[]);
97 let Some(q2) = after.iter().position(|&b| b == b'\'') else {
98 return err(ErrorKind::InvalidCharset, value.len());
99 };
100 let language = after.get(..q2).unwrap_or(&[]);
101 if let Some(i) = language
102 .iter()
103 .position(|&b| !(b.is_ascii_alphanumeric() || b == b'-'))
104 {
105 return err(ErrorKind::InvalidCharset, q1 + 1 + i);
106 }
107 let encoded_at = q1 + 1 + q2 + 1;
108 let encoded = after.get(q2 + 1..).unwrap_or(&[]);
109 let mut i = 0usize;
110 while let Some(&b) = encoded.get(i) {
111 if b == b'%' {
112 match (encoded.get(i + 1), encoded.get(i + 2)) {
113 (Some(&h), Some(&l)) if class::is_hexdig(h) && class::is_hexdig(l) => i += 3,
114 _ => return err(ErrorKind::InvalidPercentEncoding, encoded_at + i),
115 }
116 } else if class::is_attr_char(b) {
117 i += 1;
118 } else if crate::scan::is_control(b) {
119 return err(control_kind(b), encoded_at + i);
120 } else {
121 return err(ErrorKind::InvalidByte, encoded_at + i);
122 }
123 }
124 Ok(Self {
125 charset,
126 language,
127 encoded,
128 })
129 }
130
131 #[must_use]
133 pub const fn charset(&self) -> Charset {
134 self.charset
135 }
136
137 #[must_use]
139 pub const fn language(&self) -> &'a [u8] {
140 self.language
141 }
142
143 #[must_use]
145 pub const fn encoded(&self) -> &'a [u8] {
146 self.encoded
147 }
148
149 pub(crate) fn raw_bytes(&self) -> PctBytes<'a> {
151 PctBytes {
152 rest: self.encoded.iter(),
153 }
154 }
155
156 #[must_use]
159 pub fn max_decoded_len(&self) -> usize {
160 let n = self.raw_bytes().count();
161 match self.charset {
162 Charset::Utf8 => n,
163 Charset::Iso8859_1 => n.saturating_mul(2),
164 }
165 }
166
167 pub(crate) fn check_text(&self) -> Result<()> {
170 decoded_text_len(self.raw_bytes(), self.charset).map(|_| ())
171 }
172
173 pub fn decode_into<'b>(&self, out: &'b mut [u8]) -> Result<&'b str> {
184 decode_checked(self.raw_bytes(), self.charset, out)
185 }
186}
187
188pub(crate) fn decode_checked(
191 bytes: impl Iterator<Item = u8> + Clone,
192 charset: Charset,
193 out: &mut [u8],
194) -> Result<&str> {
195 let len = decoded_text_len(bytes.clone(), charset)?;
196 let Some(dst) = out.get_mut(..len) else {
197 return err(ErrorKind::BufferTooSmall, 0);
198 };
199 let mut slots = dst.iter_mut();
200 for b in bytes {
201 match charset {
202 Charset::Utf8 => {
203 if let Some(s) = slots.next() {
204 *s = b;
205 }
206 }
207 Charset::Iso8859_1 => {
208 if b < 0x80 {
209 if let Some(s) = slots.next() {
210 *s = b;
211 }
212 } else {
213 if let Some(s) = slots.next() {
215 *s = 0xc0 | (b >> 6);
216 }
217 if let Some(s) = slots.next() {
218 *s = 0x80 | (b & 0x3f);
219 }
220 }
221 }
222 }
223 }
224 core::str::from_utf8(dst)
225 .map_err(|e| crate::ParseError::new(ErrorKind::InvalidUtf8, e.valid_up_to()))
226}
227
228pub(crate) fn decoded_text_len(bytes: impl Iterator<Item = u8>, charset: Charset) -> Result<usize> {
231 let mut len = 0usize;
232 match charset {
233 Charset::Utf8 => {
234 let mut v = Utf8::new();
235 let mut point = 0u32;
236 for (i, b) in bytes.enumerate() {
237 if !v.push(b) {
238 return err(ErrorKind::InvalidUtf8, i);
239 }
240 point = match b {
243 0x00..=0x7f => u32::from(b),
244 0x80..=0xbf => (point << 6) | u32::from(b & 0x3f),
245 0xc0..=0xdf => u32::from(b & 0x1f),
246 0xe0..=0xef => u32::from(b & 0x0f),
247 _ => u32::from(b & 0x07),
248 };
249 if v.is_complete() && is_control_point(point) {
250 return err(ErrorKind::ControlInDecoded, i);
251 }
252 len += 1;
253 }
254 if !v.is_complete() {
255 return err(ErrorKind::InvalidUtf8, len);
256 }
257 }
258 Charset::Iso8859_1 => {
259 for (i, b) in bytes.enumerate() {
260 if is_control_point(u32::from(b)) {
261 return err(ErrorKind::ControlInDecoded, i);
262 }
263 len += if b < 0x80 { 1 } else { 2 };
264 }
265 }
266 }
267 Ok(len)
268}
269
270#[derive(Clone)]
272pub(crate) struct PctBytes<'a> {
273 pub(crate) rest: core::slice::Iter<'a, u8>,
274}
275
276impl Iterator for PctBytes<'_> {
277 type Item = u8;
278
279 fn next(&mut self) -> Option<u8> {
280 let b = *self.rest.next()?;
281 if b != b'%' {
282 return Some(b);
283 }
284 let hi = self.rest.next().copied().and_then(class::hex_value);
285 let lo = self.rest.next().copied().and_then(class::hex_value);
286 match (hi, lo) {
287 (Some(h), Some(l)) => Some((h << 4) | l),
288 _ => Some(b),
289 }
290 }
291}
292
293pub fn write_ext_value(text: &str, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
312 for (i, c) in text.char_indices() {
313 if is_control_point(u32::from(c)) {
314 return Err(WriteError::forbidden(i));
315 }
316 }
317 let set = EncodeSet::ATTR_CHAR_COMPLEMENT;
318 reserve(
319 out,
320 7usize.saturating_add(crate::pct::encoded_len(text.as_bytes(), &set)),
321 )?;
322 out.put(b"UTF-8''")?;
323 crate::pct::encode(text.as_bytes(), &set, HexCase::Upper, out)
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 fn decode(v: &[u8]) -> Result<std::string::String> {
331 let e = ExtValue::parse(v)?;
332 let mut buf = [0u8; 64];
333 e.decode_into(&mut buf).map(std::string::String::from)
334 }
335
336 #[test]
337 fn test_ext_value_charset_matrix() {
338 assert_eq!(decode(b"UTF-8''").unwrap(), "");
339 assert_eq!(decode(b"utf-8''a").unwrap(), "a");
340 assert_eq!(decode(b"UTF-8'en'a").unwrap(), "a");
341 assert_eq!(decode(b"ISO-8859-1''%E9").unwrap(), "\u{e9}");
342 assert_eq!(decode(b"iso-8859-1''caf%e9").unwrap(), "caf\u{e9}");
343 assert_eq!(decode(b"UTF-8''%C3%A9").unwrap(), "\u{e9}");
344 assert_eq!(
346 decode(b"UTF-8''%E9").unwrap_err().kind(),
347 ErrorKind::InvalidUtf8
348 );
349 assert_eq!(decode(b"ISO-8859-1''%C3%A9").unwrap(), "\u{c3}\u{a9}");
350 assert_eq!(
351 ExtValue::parse(b"UTF-16''a").unwrap_err().kind(),
352 ErrorKind::UnsupportedCharset
353 );
354 assert_eq!(
355 ExtValue::parse(b"''a").unwrap_err().kind(),
356 ErrorKind::InvalidCharset
357 );
358 assert_eq!(
359 ExtValue::parse(b"UTF-8'a").unwrap_err().kind(),
360 ErrorKind::InvalidCharset
361 );
362 assert_eq!(
363 ExtValue::parse(b"UTF 8''a").unwrap_err().kind(),
364 ErrorKind::InvalidCharset
365 );
366 assert_eq!(
367 ExtValue::parse(b"UTF-8'e n'a").unwrap_err().kind(),
368 ErrorKind::InvalidCharset
369 );
370 }
371
372 #[test]
373 fn test_ext_value_percent_crlf_refused() {
374 assert_eq!(
375 ExtValue::parse(b"UTF-8''%").unwrap_err().kind(),
376 ErrorKind::InvalidPercentEncoding
377 );
378 assert_eq!(
379 ExtValue::parse(b"UTF-8''%G0").unwrap_err().kind(),
380 ErrorKind::InvalidPercentEncoding
381 );
382 assert_eq!(
383 decode(b"UTF-8''%C3").unwrap_err().kind(),
384 ErrorKind::InvalidUtf8
385 );
386 assert_eq!(
387 decode(b"UTF-8''%C0%AF").unwrap_err().kind(),
388 ErrorKind::InvalidUtf8
389 );
390 assert_eq!(
391 decode(b"UTF-8''%00").unwrap_err().kind(),
392 ErrorKind::ControlInDecoded
393 );
394 assert_eq!(
395 decode(b"UTF-8''a%0D%0Ab").unwrap_err().kind(),
396 ErrorKind::ControlInDecoded
397 );
398 assert_eq!(
399 decode(b"UTF-8''%C2%85").unwrap_err().kind(),
400 ErrorKind::ControlInDecoded
401 );
402 assert_eq!(
403 decode(b"ISO-8859-1''%85").unwrap_err().kind(),
404 ErrorKind::ControlInDecoded
405 );
406 assert_eq!(decode(b"UTF-8''a%2Fb").unwrap(), "a/b");
407 assert_eq!(
408 ExtValue::parse(b"UTF-8''a b").unwrap_err().kind(),
409 ErrorKind::InvalidByte
410 );
411 assert_eq!(
412 ExtValue::parse(b"UTF-8''a\rb").unwrap_err().kind(),
413 ErrorKind::BareCr
414 );
415 }
416
417 #[test]
418 fn test_ext_value_buffer_and_lengths() {
419 let e = ExtValue::parse(b"ISO-8859-1''%E9%E9").unwrap();
420 assert_eq!(e.max_decoded_len(), 4);
421 let mut short = [0xaau8; 3];
422 assert_eq!(
423 e.decode_into(&mut short).unwrap_err().kind(),
424 ErrorKind::BufferTooSmall
425 );
426 assert_eq!(short, [0xaa; 3]);
427 let u = ExtValue::parse(b"UTF-8''%E2%82%AC").unwrap();
428 assert_eq!(u.max_decoded_len(), 3);
429 }
430
431 #[test]
432 fn test_write_ext_value_round_trip_and_refusals() {
433 let mut buf = [0u8; 128];
434 let mut out = crate::SliceSink::new(&mut buf);
435 assert_eq!(
436 write_ext_value("a\r\nb", &mut out),
437 Err(WriteError::Forbidden { at: 1 })
438 );
439 assert_eq!(
440 write_ext_value("x\u{85}", &mut out),
441 Err(WriteError::Forbidden { at: 1 })
442 );
443 write_ext_value("caf\u{e9} \u{1f600}/", &mut out).unwrap();
444 let back = ExtValue::parse(out.written()).unwrap();
445 let mut dec = [0u8; 64];
446 assert_eq!(back.decode_into(&mut dec).unwrap(), "caf\u{e9} \u{1f600}/");
447 }
448}