1use super::cfws;
5use crate::{
6 class,
7 error::{ErrorKind, ParseError, Result, WriteError, err},
8 ext_value::{Charset, ExtValue, is_mime_attribute_char},
9 limits::{Limits, MAX_RFC2231_SECTIONS},
10 param::{MAX_CHECKED_PARAMS, Names},
11 scan::control_kind,
12 text::{Word, parse_quoted},
13 write::{CountSink, Sink, reserve},
14};
15
16const RELAXED: Limits = Limits::DEFAULT
18 .max_params(u16::MAX)
19 .max_comment_depth(u8::MAX);
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23enum Section {
24 Plain,
26 Ext,
28 Part { n: usize, ext: bool },
30}
31
32fn split_name(name: &[u8]) -> (&[u8], Section) {
34 let (body, ext) = match name.split_last() {
35 Some((b'*', body)) => (body, true),
36 _ => (name, false),
37 };
38 if let Some(star) = body.iter().rposition(|&b| b == b'*') {
39 let digits = body.get(star + 1..).unwrap_or(&[]);
40 let canonical = !digits.is_empty()
41 && digits.iter().all(u8::is_ascii_digit)
42 && (digits.len() == 1 || digits.first() != Some(&b'0'));
43 if canonical {
44 let n = digits.iter().fold(0usize, |a, &d| {
46 a.saturating_mul(10).saturating_add(usize::from(d - b'0'))
47 });
48 return (body.get(..star).unwrap_or(&[]), Section::Part { n, ext });
49 }
50 }
51 if ext {
52 (body, Section::Ext)
53 } else {
54 (name, Section::Plain)
55 }
56}
57
58fn refused(v: &[u8], at: usize, kind: ErrorKind) -> ParseError {
59 match v.get(at) {
60 Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
61 _ => ParseError::new(kind, at),
62 }
63}
64
65fn param_at<'a>(
68 v: &'a [u8],
69 i: usize,
70 limits: &Limits,
71) -> Result<(&'a [u8], Word<'a>, usize, usize)> {
72 let j = cfws(v, i + 1, limits)?;
73 let n = v
74 .get(j..)
75 .unwrap_or(&[])
76 .iter()
77 .take_while(|&&b| class::is_mime_token_char(b))
78 .count();
79 if n == 0 {
80 return Err(refused(v, j, ErrorKind::NotToken));
81 }
82 let name = v.get(j..j + n).unwrap_or(&[]);
83 let k = cfws(v, j + n, limits)?;
84 if v.get(k) != Some(&b'=') {
85 return Err(refused(v, k, ErrorKind::MissingValue));
86 }
87 let at = cfws(v, k + 1, limits)?;
88 let rest = v.get(at..).unwrap_or(&[]);
89 if rest.first() == Some(&b'"') {
90 let (q, used) = parse_quoted(rest).map_err(|e| e.shifted(at))?;
91 return Ok((name, Word::Quoted(q), at, at + used));
92 }
93 let t = rest
94 .iter()
95 .take_while(|&&b| class::is_mime_token_char(b))
96 .count();
97 if t == 0 {
98 return Err(refused(v, at, ErrorKind::MissingValue));
99 }
100 Ok((name, Word::Token(rest.get(..t).unwrap_or(&[])), at, at + t))
101}
102
103fn pct_chars(t: &[u8]) -> Option<usize> {
105 let mut i = 0usize;
106 while let Some(&b) = t.get(i) {
107 if b == b'%' {
108 match (t.get(i + 1), t.get(i + 2)) {
109 (Some(&h), Some(&l)) if class::is_hexdig(h) && class::is_hexdig(l) => i += 3,
110 _ => return Some(i),
111 }
112 } else if is_mime_attribute_char(b) {
113 i += 1;
114 } else {
115 return Some(i);
116 }
117 }
118 None
119}
120
121pub fn parse_mime_params<'a>(s: &'a [u8], limits: &Limits) -> Result<(MimeParams<'a>, usize)> {
151 let (mut i, mut count) = (0usize, 0usize);
152 let max = usize::from(limits.params).min(MAX_CHECKED_PARAMS);
153 let mut names = Names::new();
154 loop {
155 let j = cfws(s, i, limits)?;
156 if s.get(j) != Some(&b';') {
157 break;
158 }
159 let (name, value, at, end) = param_at(s, j, limits)?;
160 count += 1;
161 if count > max {
162 return err(ErrorKind::TooManyMembers, j);
163 }
164 let name_at = (name.as_ptr() as usize).saturating_sub(s.as_ptr() as usize);
165 if names.repeats(s, name_at, name, class::is_mime_token_char) {
166 return err(ErrorKind::DuplicateParameter, j);
167 }
168 match (split_name(name).1, value) {
169 (Section::Ext | Section::Part { n: 0, ext: true }, Word::Token(t)) => {
170 let ext = ExtValue::parse_mime(t).map_err(|e| e.shifted(at))?;
171 if split_name(name).1 == Section::Ext {
172 ext.check_text()
173 .map_err(|e| ParseError::new(e.kind(), at))?;
174 }
175 }
176 (Section::Part { ext: true, .. }, Word::Token(t)) => {
177 if let Some(bad) = pct_chars(t) {
178 return err(ErrorKind::InvalidPercentEncoding, at + bad);
179 }
180 }
181 (Section::Ext | Section::Part { ext: true, .. }, Word::Quoted(_)) => {
182 return err(ErrorKind::InvalidCharset, at);
184 }
185 _ => {}
186 }
187 i = end;
188 }
189 let params = MimeParams {
190 text: s.get(..i).unwrap_or(&[]),
191 count,
192 };
193 for (name, _) in params.iter() {
195 if let (base, Section::Part { n: 0, .. }) = split_name(name) {
196 let c = Continuation { params, name: base };
197 let _ = c.walk(limits, &mut |_| {})?;
198 } else if let (base, Section::Part { .. }) = split_name(name) {
199 let has_zero = params
200 .iter()
201 .any(|(n, _)| matches!(split_name(n), (b, Section::Part { n: 0, .. }) if b.eq_ignore_ascii_case(base)));
202 if !has_zero {
203 let at = (name.as_ptr() as usize).saturating_sub(s.as_ptr() as usize);
204 return err(ErrorKind::SectionGap, at);
205 }
206 }
207 }
208 Ok((params, i))
209}
210
211#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
224pub struct MimeParams<'a> {
225 text: &'a [u8],
226 count: usize,
227}
228
229impl<'a> MimeParams<'a> {
230 #[must_use]
245 pub fn get(&self, name: &str) -> Option<MimeValue<'a>> {
246 let want = name.as_bytes();
247 let mut ext = None;
248 let mut plain = None;
249 for (n, w) in self.iter() {
250 match split_name(n) {
251 (b, Section::Part { n: 0, .. }) if b.eq_ignore_ascii_case(want) => {
252 return Some(MimeValue::Continued(Continuation {
253 params: *self,
254 name: b,
255 }));
256 }
257 (b, Section::Ext) if b.eq_ignore_ascii_case(want) => {
258 if let Word::Token(t) = w {
259 ext = ExtValue::parse_mime(t).ok();
260 }
261 }
262 (b, Section::Plain) if b.eq_ignore_ascii_case(want) => plain = Some(w),
263 _ => {}
264 }
265 }
266 ext.map(MimeValue::Extended).or(plain.map(MimeValue::Plain))
267 }
268
269 pub fn decode_into<'b>(&self, name: &str, out: &'b mut [u8]) -> Result<Option<&'b str>> {
290 match self.get(name) {
291 None => Ok(None),
292 Some(MimeValue::Continued(c)) => c.decode_into(out).map(Some),
293 Some(MimeValue::Extended(e)) => e.decode_into(out).map(Some),
294 Some(MimeValue::Plain(w)) => {
295 let Some(dst) = out.get_mut(..w.len()) else {
296 return err(ErrorKind::BufferTooSmall, 0);
297 };
298 let (mut utf8, text) = (crate::utf8::Utf8::new(), self.value_text(&w));
300 let valid = match w {
301 Word::Token(t) => t.iter().all(|&b| utf8.push(b)),
302 Word::Quoted(q) => q.bytes().all(|b| utf8.push(b)),
303 };
304 if !valid || !utf8.is_complete() {
305 return err(ErrorKind::InvalidUtf8, text);
306 }
307 core::str::from_utf8(w.unescape_into(dst)?)
308 .map(Some)
309 .map_err(|e| ParseError::new(ErrorKind::InvalidUtf8, e.valid_up_to()))
310 }
311 }
312 }
313
314 fn value_text(&self, w: &Word<'_>) -> usize {
316 let raw = match w {
317 Word::Token(t) => t,
318 Word::Quoted(q) => q.raw(),
319 };
320 (raw.as_ptr() as usize).saturating_sub(self.text.as_ptr() as usize)
321 }
322
323 #[must_use]
336 pub fn iter(&self) -> MimeParamIter<'a> {
337 MimeParamIter { s: self.text, i: 0 }
338 }
339
340 #[must_use]
352 pub const fn len(&self) -> usize {
353 self.count
354 }
355
356 #[must_use]
368 pub const fn is_empty(&self) -> bool {
369 self.count == 0
370 }
371}
372
373#[derive(Clone, Debug)]
388pub struct MimeParamIter<'a> {
389 s: &'a [u8],
390 i: usize,
391}
392
393impl<'a> Iterator for MimeParamIter<'a> {
394 type Item = (&'a [u8], Word<'a>);
395
396 fn next(&mut self) -> Option<Self::Item> {
397 let j = cfws(self.s, self.i, &RELAXED).ok()?;
398 if self.s.get(j) != Some(&b';') {
399 return None;
400 }
401 let (name, value, _, end) = param_at(self.s, j, &RELAXED).ok()?;
402 self.i = end;
403 Some((name, value))
404 }
405}
406
407#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
420pub enum MimeValue<'a> {
421 Plain(Word<'a>),
423 Extended(ExtValue<'a>),
425 Continued(Continuation<'a>),
427}
428
429#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
443pub struct Continuation<'a> {
444 params: MimeParams<'a>,
445 name: &'a [u8],
446}
447
448impl Continuation<'_> {
449 #[must_use]
462 pub fn sections(&self) -> usize {
463 self.params
464 .iter()
465 .filter(|(n, _)| matches!(split_name(n), (b, Section::Part { .. }) if b.eq_ignore_ascii_case(self.name)))
466 .count()
467 }
468
469 #[must_use]
485 pub fn max_decoded_len(&self) -> usize {
486 self.params
487 .iter()
488 .filter(|(n, _)| matches!(split_name(n), (b, Section::Part { .. }) if b.eq_ignore_ascii_case(self.name)))
489 .map(|(_, w)| w.len().saturating_mul(2))
490 .sum()
491 }
492
493 pub fn decode_into<'b>(&self, out: &'b mut [u8]) -> Result<&'b str> {
512 let len = self.walk(&RELAXED, &mut |_| {})?;
513 let Some(dst) = out.get_mut(..len) else {
514 return err(ErrorKind::BufferTooSmall, 0);
515 };
516 let mut n = 0usize;
517 let _ = self.walk(&RELAXED, &mut |b: u8| {
518 if let Some(d) = dst.get_mut(n) {
519 *d = b;
520 }
521 n += 1;
522 })?;
523 core::str::from_utf8(dst)
524 .map_err(|e| ParseError::new(ErrorKind::InvalidUtf8, e.valid_up_to()))
525 }
526
527 fn walk(&self, limits: &Limits, emit: &mut impl FnMut(u8)) -> Result<usize> {
531 let base = self.params.text.as_ptr() as usize;
532 let at = |s: &[u8]| (s.as_ptr() as usize).saturating_sub(base);
533 let mut slots: [Option<(bool, Word<'_>)>; MAX_RFC2231_SECTIONS as usize] =
534 [None; MAX_RFC2231_SECTIONS as usize];
535 let max = usize::from(limits.rfc2231_sections.min(MAX_RFC2231_SECTIONS));
536 let mut highest = 0usize;
537 for (name, w) in self.params.iter() {
538 if let (b, Section::Part { n, ext }) = split_name(name) {
539 if !b.eq_ignore_ascii_case(self.name) {
540 continue;
541 }
542 if n >= max {
543 return err(ErrorKind::TooManySections, at(name));
544 }
545 match slots.get_mut(n) {
546 Some(Some(_)) => return err(ErrorKind::SectionGap, at(name)),
547 Some(slot) => *slot = Some((ext, w)),
548 None => {}
549 }
550 highest = highest.max(n);
551 }
552 }
553 let used = slots.get(..=highest).unwrap_or(&[]);
554 if let Some(gap) = used.iter().position(Option::is_none) {
555 let after = self.params.iter().find(|(name, _)| {
557 matches!(split_name(name), (b, Section::Part { n, .. })
558 if b.eq_ignore_ascii_case(self.name) && n > gap)
559 });
560 let offset = after.map_or(0, |(name, _)| at(name));
561 return err(ErrorKind::SectionGap, offset);
562 }
563 let mut charset = Charset::Utf8;
564 let mut utf8 = crate::utf8::Utf8::new();
565 let (mut count, mut prev) = (0usize, 0u8);
566 let mut put = |b: u8, charset: Charset, strict: bool, off: usize| -> Result<()> {
569 if (b < 0x20 && (strict || b != b'\t')) || b == 0x7f {
570 return err(ErrorKind::ControlInDecoded, off);
571 }
572 if charset == Charset::UsAscii && b >= 0x80 {
573 return err(ErrorKind::InvalidByte, off);
574 }
575 let c1 = if charset == Charset::Iso8859_1 {
576 (0x80..=0x9f).contains(&b)
577 } else {
578 prev == 0xc2 && (0x80..=0x9f).contains(&b)
579 };
580 if c1 {
581 return err(ErrorKind::ControlInDecoded, off);
582 }
583 prev = b;
584 let decoded = [0xc0 | (b >> 6), 0x80 | (b & 0x3f)];
585 let bytes: &[u8] = if charset == Charset::Iso8859_1 && b >= 0x80 {
586 &decoded
587 } else {
588 core::slice::from_ref(&b)
589 };
590 for &x in bytes {
591 if !utf8.push(x) {
592 return err(ErrorKind::InvalidUtf8, off);
593 }
594 emit(x);
595 count += 1;
596 }
597 Ok(())
598 };
599 for (k, slot) in used.iter().enumerate() {
600 let Some((ext, w)) = *slot else { continue };
601 let raw: &[u8] = match (k, ext, w) {
602 (0, true, Word::Token(t)) => {
603 let e = ExtValue::parse_mime(t)?;
604 charset = e.charset();
605 e.encoded()
606 }
607 (_, true, Word::Token(t)) => t,
608 _ => &[],
609 };
610 if ext {
611 let mut i = 0usize;
612 while let Some(&b) = raw.get(i) {
613 let start = i;
614 let byte = if b == b'%' {
615 let hi = raw
616 .get(i + 1)
617 .copied()
618 .and_then(class::hex_value)
619 .unwrap_or(0);
620 let lo = raw
621 .get(i + 2)
622 .copied()
623 .and_then(class::hex_value)
624 .unwrap_or(0);
625 i += 3;
626 (hi << 4) | lo
627 } else {
628 i += 1;
629 b
630 };
631 put(byte, charset, true, at(raw) + start)?;
632 }
633 } else {
634 match w {
635 Word::Token(t) => {
636 for (i, &byte) in t.iter().enumerate() {
637 put(byte, charset, false, at(t) + i)?;
638 }
639 }
640 Word::Quoted(q) => {
641 for byte in q.bytes() {
642 put(byte, charset, false, at(q.raw()))?;
643 }
644 }
645 }
646 }
647 }
648 if !utf8.is_complete() {
649 return err(ErrorKind::InvalidUtf8, self.params.text.len());
650 }
651 Ok(count)
652 }
653}
654
655fn is_attribute(name: &[u8]) -> bool {
658 !name.is_empty()
659 && name
660 .iter()
661 .all(|&b| class::is_mime_token_char(b) && !matches!(b, b'*' | b'\'' | b'%'))
662}
663
664const LINE: usize = 78;
666
667const fn unit_len(b: u8) -> usize {
669 if class::is_attr_char(b) { 1 } else { 3 }
670}
671
672fn put_unit(b: u8, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
674 const HEX: &[u8; 16] = b"0123456789ABCDEF";
675 if class::is_attr_char(b) {
676 out.put(&[b])?;
677 } else {
678 let hi = HEX.get(usize::from(b >> 4)).copied().unwrap_or(b'0');
679 let lo = HEX.get(usize::from(b & 0xf)).copied().unwrap_or(b'0');
680 out.put(&[b'%', hi, lo])?;
681 }
682 Ok(())
683}
684
685#[derive(Clone, Copy)]
687enum Form {
688 Token,
689 Quoted,
690 Extended,
691}
692
693fn form(n: &[u8], v: &[u8]) -> (Form, usize) {
695 if !v.is_empty() && v.iter().all(|&b| class::is_mime_token_char(b)) {
698 return (Form::Token, 3 + n.len() + v.len());
699 }
700 if v.iter().all(|&b| (0x20..=0x7e).contains(&b)) {
701 let escaped = v.iter().filter(|&&b| b == b'"' || b == b'\\').count();
702 return (Form::Quoted, 5 + n.len() + v.len() + escaped);
703 }
704 let encoded: usize = v.iter().map(|&b| unit_len(b)).sum();
705 (Form::Extended, 11 + n.len() + encoded)
706}
707
708fn put_form(
710 form: Form,
711 n: &[u8],
712 v: &[u8],
713 out: &mut impl Sink,
714) -> core::result::Result<(), WriteError> {
715 out.put(n)?;
716 match form {
717 Form::Token => {
718 out.put(b"=")?;
719 out.put(v)?;
720 }
721 Form::Quoted => {
722 out.put(b"=\"")?;
723 let mut rest = v;
725 while let Some(p) = rest.iter().position(|&b| b == b'"' || b == b'\\') {
726 out.put(rest.get(..p).unwrap_or(&[]))?;
727 out.put(b"\\")?;
728 out.put(rest.get(p..p + 1).unwrap_or(&[]))?;
729 rest = rest.get(p + 1..).unwrap_or(&[]);
730 }
731 out.put(rest)?;
732 out.put(b"\"")?;
733 }
734 Form::Extended => {
735 out.put(b"*=UTF-8''")?;
736 let mut rest = v;
737 while let Some(p) = rest.iter().position(|&b| !class::is_attr_char(b)) {
738 out.put(rest.get(..p).unwrap_or(&[]))?;
739 put_unit(rest.get(p).copied().unwrap_or(0), out)?;
740 rest = rest.get(p + 1..).unwrap_or(&[]);
741 }
742 out.put(rest)?;
743 }
744 }
745 Ok(())
746}
747
748fn put_param(
752 n: &[u8],
753 v: &[u8],
754 (f, len): (Form, usize),
755 room: usize,
756 out: &mut impl Sink,
757) -> core::result::Result<usize, WriteError> {
758 if len <= room {
759 out.put(b"; ")?;
760 put_form(f, n, v, out)?;
761 return Ok(room - len);
762 }
763 if len <= LINE {
766 out.put(b";\r\n ")?;
767 put_form(f, n, v, out)?;
768 return Ok(LINE - (len - 1));
769 }
770 if 1 + n.len() + 12 + 3 + 1 > LINE {
774 return Err(WriteError::OutOfRange);
775 }
776 out.put(b";")?;
777 let mut rest = v;
778 let mut k = 0usize;
779 let mut col = 0usize;
780 while !rest.is_empty() || k == 0 {
781 if k >= usize::from(MAX_RFC2231_SECTIONS) {
782 return Err(WriteError::OutOfRange);
783 }
784 let mut digits = [0u8; 2];
785 let dl = if k < 10 {
786 digits[0] = b'0' + k as u8;
787 1
788 } else {
789 digits[0] = b'0' + (k / 10) as u8;
790 digits[1] = b'0' + (k % 10) as u8;
791 2
792 };
793 if k > 0 {
794 out.put(b";")?;
795 }
796 out.put(b"\r\n ")?;
797 out.put(n)?;
798 out.put(b"*")?;
799 out.put(digits.get(..dl).unwrap_or(&[]))?;
800 out.put(b"*=")?;
801 col = 1 + n.len() + 1 + dl + 2;
802 if k == 0 {
803 out.put(b"UTF-8''")?;
804 col += 7;
805 }
806 let mut took = 0usize;
807 for &b in rest {
808 let u = unit_len(b);
809 if col + u + 1 > LINE && took > 0 {
811 break;
812 }
813 put_unit(b, out)?;
814 col += u;
815 took += 1;
816 }
817 rest = rest.get(took..).unwrap_or(&[]);
818 k += 1;
819 }
820 Ok(LINE.saturating_sub(col))
821}
822
823fn write_param(
826 name: &str,
827 value: &str,
828 room: usize,
829 out: &mut impl Sink,
830) -> core::result::Result<usize, WriteError> {
831 if !is_attribute(name.as_bytes()) {
832 return Err(WriteError::NotToken);
833 }
834 let control = if value.is_ascii() {
836 value.bytes().position(|b| b < 0x20 || b == 0x7f)
837 } else {
838 value
839 .char_indices()
840 .find(|&(_, c)| c.is_control())
841 .map(|(at, _)| at)
842 };
843 if let Some(at) = control {
844 return Err(WriteError::forbidden(at));
845 }
846 let (n, v) = (name.as_bytes(), value.as_bytes());
847 let f = form(n, v);
848 let mut count = CountSink::new();
849 let _ = put_param(n, v, f, room, &mut count)?;
850 reserve(out, count.count())?;
851 put_param(n, v, f, room, out)
852}
853
854pub fn write_mime_param(
881 name: &str,
882 value: &str,
883 out: &mut impl Sink,
884) -> core::result::Result<(), WriteError> {
885 write_param(name, value, LINE, out).map(|_| ())
886}
887
888pub fn write_mime_param_at(
916 name: &str,
917 value: &str,
918 room_first_line: usize,
919 out: &mut impl Sink,
920) -> core::result::Result<usize, WriteError> {
921 write_param(name, value, room_first_line, out)
922}
923
924#[cfg(test)]
925mod tests {
926 use super::*;
927 use std::{format, string::String, vec, vec::Vec};
928
929 fn p(v: &[u8]) -> MimeParams<'_> {
930 parse_mime_params(v, &Limits::DEFAULT).unwrap().0
931 }
932
933 fn k(v: &[u8]) -> ErrorKind {
934 parse_mime_params(v, &Limits::DEFAULT).unwrap_err().kind()
935 }
936
937 fn get(v: &[u8], name: &str) -> String {
938 let mut buf = vec![0u8; 512];
939 String::from(p(v).decode_into(name, &mut buf).unwrap().unwrap())
940 }
941
942 #[test]
943 fn test_mime_params_basic() {
944 assert_eq!(get(b"; a=b; c=\"d e\"", "C"), "d e");
945 assert_eq!(get(b"; boundary={x}", "boundary"), "{x}");
946 assert_eq!(get(b" ; a = (c) b (d)", "a"), "b");
947 assert_eq!(get(b";\r\n a=b", "a"), "b");
948 assert!(
949 p(b"; a=b")
950 .decode_into("z", &mut [0u8; 4])
951 .unwrap()
952 .is_none()
953 );
954 assert_eq!(parse_mime_params(b"", &Limits::DEFAULT).unwrap().1, 0);
955 assert_eq!(
956 parse_mime_params(b"; a=b x", &Limits::DEFAULT).unwrap().1,
957 5
958 );
959 assert_eq!(k(b"; =b"), ErrorKind::NotToken);
960 assert_eq!(k(b"; a"), ErrorKind::MissingValue);
961 assert_eq!(k(b"; a="), ErrorKind::MissingValue);
962 assert_eq!(k(b"; a=\"b"), ErrorKind::UnterminatedQuote);
963 assert_eq!(k(b"; a=1; A=2"), ErrorKind::DuplicateParameter);
964 assert_eq!(k(b"; a=1 (x"), ErrorKind::UnbalancedComment);
965 let l = Limits::DEFAULT.max_params(2);
966 assert!(parse_mime_params(b"; a=1; b=2", &l).is_ok());
967 assert_eq!(
968 parse_mime_params(b"; a=1; b=2; c=3", &l)
969 .unwrap_err()
970 .kind(),
971 ErrorKind::TooManyMembers
972 );
973 }
974
975 #[test]
976 fn test_mime_params_rfc2231() {
977 assert_eq!(
978 get(
979 b"; t*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A",
980 "t"
981 ),
982 "This is ***fun***"
983 );
984 assert_eq!(get(b"; t*0=\"foo\"; t*1=bar", "t"), "foobar");
985 assert_eq!(get(b"; t*1=bar; t*0=\"foo\"", "t"), "foobar");
986 assert_eq!(get(b"; t*0*=UTF-8''%E2%82; t*1*=%AC", "t"), "\u{20ac}");
987 assert_eq!(get(b"; t=plain; t*=UTF-8''ext", "t"), "ext");
988 assert_eq!(get(b"; t=plain; t*0=cont", "t"), "cont");
989 assert_eq!(get(b"; t*00=x; t=plain", "t"), "plain");
990 assert_eq!(k(b"; t*0=a; t*2=c"), ErrorKind::SectionGap);
991 assert_eq!(k(b"; t*1=a"), ErrorKind::SectionGap);
992 assert_eq!(k(b"; t*0=a; t*0=b"), ErrorKind::DuplicateParameter);
993 assert_eq!(
994 k(b"; t*0*=UTF-8''a; t*1*=UTF-8''b"),
995 ErrorKind::InvalidPercentEncoding
996 );
997 assert_eq!(k(b"; t*0*=UTF-8''%C3"), ErrorKind::InvalidUtf8);
998 assert_eq!(k(b"; t*0*=UTF-8''%0D"), ErrorKind::ControlInDecoded);
999 assert_eq!(k(b"; t*=\"UTF-8''x\""), ErrorKind::InvalidCharset);
1000 let mut v = Vec::new();
1001 for i in 0..32 {
1002 v.extend_from_slice(format!("; t*{i}=x").as_bytes());
1003 }
1004 assert_eq!(
1005 p(&v)
1006 .get("t")
1007 .map(|m| matches!(m, MimeValue::Continued(c) if c.sections() == 32)),
1008 Some(true)
1009 );
1010 v.extend_from_slice(b"; t*32=x");
1011 assert_eq!(k(&v), ErrorKind::TooManySections);
1012 let l = Limits::DEFAULT.max_rfc2231_sections(2);
1013 assert!(parse_mime_params(b"; t*0=a; t*1=b", &l).is_ok());
1014 assert_eq!(
1015 parse_mime_params(b"; t*0=a; t*1=b; t*2=c", &l)
1016 .unwrap_err()
1017 .kind(),
1018 ErrorKind::TooManySections
1019 );
1020 }
1021
1022 fn written(name: &str, value: &str) -> Vec<u8> {
1023 let mut buf = vec![0u8; 4096];
1024 let mut out = crate::SliceSink::new(&mut buf);
1025 write_mime_param(name, value, &mut out).unwrap();
1026 out.written().to_vec()
1027 }
1028
1029 #[test]
1030 fn test_write_mime_param_forms_and_round_trip() {
1031 assert_eq!(written("charset", "utf-8"), b"; charset=utf-8");
1032 assert_eq!(written("name", "a \"b\""), b"; name=\"a \\\"b\\\"\"");
1033 assert_eq!(written("name", ""), b"; name=\"\"");
1034 let long = "x".repeat(200);
1035 let nonascii = "\u{e9}".repeat(60);
1036 for value in [
1037 String::from("plain"),
1038 long,
1039 nonascii,
1040 String::from("caf\u{e9} au lait.txt"),
1041 ] {
1042 let w = written("filename", &value);
1043 for line in w.split(|&b| b == b'\n') {
1044 assert!(line.len() <= 79, "{}", String::from_utf8_lossy(line));
1045 }
1046 let (params, used) = parse_mime_params(&w, &Limits::DEFAULT).unwrap();
1047 assert_eq!(used, w.len());
1048 let mut buf = vec![0u8; 1024];
1049 assert_eq!(
1050 params.decode_into("filename", &mut buf).unwrap(),
1051 Some(value.as_str())
1052 );
1053 }
1054 let mut buf = [0u8; 8];
1055 let mut out = crate::SliceSink::new(&mut buf);
1056 assert_eq!(
1057 write_mime_param("a*b", "x", &mut out),
1058 Err(WriteError::NotToken)
1059 );
1060 assert_eq!(
1061 write_mime_param("a", "x\ny", &mut out),
1062 Err(WriteError::Forbidden { at: 1 })
1063 );
1064 assert_eq!(
1065 write_mime_param("a", "toolongvalue", &mut out),
1066 Err(WriteError::Full)
1067 );
1068 assert!(out.is_empty());
1069 let mut big = vec![0u8; 1 << 16];
1070 let mut out = crate::SliceSink::new(&mut big);
1071 assert_eq!(
1072 write_mime_param("f", &"\u{e9}".repeat(1000), &mut out),
1073 Err(WriteError::OutOfRange)
1074 );
1075 }
1076
1077 fn written_at(name: &str, value: &str, room: usize) -> (String, usize) {
1078 let mut buf = vec![0u8; 4096];
1079 let mut out = crate::SliceSink::new(&mut buf);
1080 let left = write_mime_param_at(name, value, room, &mut out).unwrap();
1081 (String::from_utf8(out.written().to_vec()).unwrap(), left)
1082 }
1083
1084 #[test]
1085 fn test_write_mime_param_at_room() {
1086 assert_eq!(
1088 written_at("charset", "utf-8", 15),
1089 (String::from("; charset=utf-8"), 0)
1090 );
1091 assert_eq!(
1092 written_at("charset", "utf-8", 14),
1093 (String::from(";\r\n charset=utf-8"), 64)
1094 );
1095 assert_eq!(
1096 written_at("charset", "utf-8", 0),
1097 (String::from(";\r\n charset=utf-8"), 64)
1098 );
1099 assert_eq!(written_at("n", "a b", 9), (String::from("; n=\"a b\""), 0));
1100 assert_eq!(
1101 written_at("n", "a b", 8),
1102 (String::from(";\r\n n=\"a b\""), 70)
1103 );
1104 assert_eq!(
1105 written_at("f", "caf\u{e9}", 21),
1106 (String::from("; f*=UTF-8''caf%C3%A9"), 0)
1107 );
1108 assert_eq!(
1109 written_at("f", "caf\u{e9}", 20),
1110 (String::from(";\r\n f*=UTF-8''caf%C3%A9"), 58)
1111 );
1112 let v = "x".repeat(78 - 3 - 1);
1115 let (w, left) = written_at("a", &v, 78);
1116 assert_eq!((w.len(), left), (78, 0));
1117 let (w, left) = written_at("a", &v, 10);
1118 assert_eq!((w.len(), left), (80, 1));
1119 let long = "x".repeat(200);
1121 for room in [0, 10, 78] {
1122 let (w, left) = written_at("filename", &long, room);
1123 assert!(w.starts_with(";\r\n filename*0*=UTF-8''"), "{w}");
1124 let last = w.rsplit("\r\n").next().unwrap();
1125 assert_eq!(left, 78 - last.len());
1126 }
1127 let mut buf = vec![0u8; 256];
1129 let mut out = crate::SliceSink::new(&mut buf);
1130 let mut room = 78 - "Content-Type: text/plain".len();
1131 for (n, v) in [
1132 ("charset", "utf-8"),
1133 ("format", "flowed"),
1134 ("delsp", "yes"),
1135 ("name", "a long attachment name.txt"),
1136 ] {
1137 room = write_mime_param_at(n, v, room, &mut out).unwrap();
1138 }
1139 assert_eq!(
1140 out.written(),
1141 b"; charset=utf-8; format=flowed; delsp=yes;\r\n name=\"a long attachment name.txt\""
1142 );
1143 assert_eq!(room, 78 - 34);
1144 assert_eq!(
1145 write_mime_param_at("a*", "x", 78, &mut crate::SliceSink::new(&mut [0u8; 8])),
1146 Err(WriteError::NotToken)
1147 );
1148 }
1149}