1use super::cfws;
5use crate::{
6 class,
7 error::{ErrorKind, ParseError, Result, WriteError, err},
8 ext_value::{Charset, ExtValue},
9 limits::{Limits, MAX_RFC2231_SECTIONS},
10 scan::control_kind,
11 text::{Word, parse_quoted},
12 write::{CountSink, Sink, reserve},
13};
14
15const RELAXED: Limits = Limits::DEFAULT
17 .max_params(u16::MAX)
18 .max_comment_depth(u8::MAX);
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22enum Section {
23 Plain,
25 Ext,
27 Part { n: usize, ext: bool },
29}
30
31fn split_name(name: &[u8]) -> (&[u8], Section) {
33 let (body, ext) = match name.split_last() {
34 Some((b'*', body)) => (body, true),
35 _ => (name, false),
36 };
37 if let Some(star) = body.iter().rposition(|&b| b == b'*') {
38 let digits = body.get(star + 1..).unwrap_or(&[]);
39 let canonical = !digits.is_empty()
40 && digits.len() <= 3
41 && digits.iter().all(u8::is_ascii_digit)
42 && (digits.len() == 1 || digits.first() != Some(&b'0'));
43 if canonical {
44 let n = digits
45 .iter()
46 .fold(0usize, |a, &d| a * 10 + usize::from(d - b'0'));
47 return (body.get(..star).unwrap_or(&[]), Section::Part { n, ext });
48 }
49 }
50 if ext {
51 (body, Section::Ext)
52 } else {
53 (name, Section::Plain)
54 }
55}
56
57fn refused(v: &[u8], at: usize, kind: ErrorKind) -> ParseError {
58 match v.get(at) {
59 Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
60 _ => ParseError::new(kind, at),
61 }
62}
63
64fn param_at<'a>(
67 v: &'a [u8],
68 i: usize,
69 limits: &Limits,
70) -> Result<(&'a [u8], Word<'a>, usize, usize)> {
71 let j = cfws(v, i + 1, limits)?;
72 let n = v
73 .get(j..)
74 .unwrap_or(&[])
75 .iter()
76 .take_while(|&&b| class::is_mime_token_char(b))
77 .count();
78 if n == 0 {
79 return Err(refused(v, j, ErrorKind::NotToken));
80 }
81 let name = v.get(j..j + n).unwrap_or(&[]);
82 let k = cfws(v, j + n, limits)?;
83 if v.get(k) != Some(&b'=') {
84 return Err(refused(v, k, ErrorKind::MissingValue));
85 }
86 let at = cfws(v, k + 1, limits)?;
87 let rest = v.get(at..).unwrap_or(&[]);
88 if rest.first() == Some(&b'"') {
89 let (q, used) = parse_quoted(rest).map_err(|e| e.shifted(at))?;
90 return Ok((name, Word::Quoted(q), at, at + used));
91 }
92 let t = rest
93 .iter()
94 .take_while(|&&b| class::is_mime_token_char(b))
95 .count();
96 if t == 0 {
97 return Err(refused(v, at, ErrorKind::MissingValue));
98 }
99 Ok((name, Word::Token(rest.get(..t).unwrap_or(&[])), at, at + t))
100}
101
102fn pct_chars(t: &[u8]) -> Option<usize> {
104 let mut i = 0usize;
105 while let Some(&b) = t.get(i) {
106 if b == b'%' {
107 match (t.get(i + 1), t.get(i + 2)) {
108 (Some(&h), Some(&l)) if class::is_hexdig(h) && class::is_hexdig(l) => i += 3,
109 _ => return Some(i),
110 }
111 } else if class::is_attr_char(b) {
112 i += 1;
113 } else {
114 return Some(i);
115 }
116 }
117 None
118}
119
120pub fn parse_mime_params<'a>(s: &'a [u8], limits: &Limits) -> Result<(MimeParams<'a>, usize)> {
149 let (mut i, mut count) = (0usize, 0usize);
150 loop {
151 let j = cfws(s, i, limits)?;
152 if s.get(j) != Some(&b';') {
153 break;
154 }
155 let (name, value, at, end) = param_at(s, j, limits)?;
156 count += 1;
157 if count > usize::from(limits.params) {
158 return err(ErrorKind::TooManyMembers, j);
159 }
160 let earlier = MimeParams {
161 text: s.get(..j).unwrap_or(&[]),
162 count: count - 1,
163 };
164 if earlier.iter().any(|(n, _)| n.eq_ignore_ascii_case(name)) {
165 return err(ErrorKind::DuplicateParameter, j);
166 }
167 match (split_name(name).1, value) {
168 (Section::Ext | Section::Part { n: 0, ext: true }, Word::Token(t)) => {
169 let ext = ExtValue::parse(t).map_err(|e| e.shifted(at))?;
170 if split_name(name).1 == Section::Ext {
171 ext.check_text()
172 .map_err(|e| ParseError::new(e.kind(), at))?;
173 }
174 }
175 (Section::Part { ext: true, .. }, Word::Token(t)) => {
176 if let Some(bad) = pct_chars(t) {
177 return err(ErrorKind::InvalidPercentEncoding, at + bad);
178 }
179 }
180 (Section::Ext | Section::Part { ext: true, .. }, Word::Quoted(_)) => {
181 return err(ErrorKind::InvalidCharset, at);
183 }
184 _ => {}
185 }
186 i = end;
187 }
188 let params = MimeParams {
189 text: s.get(..i).unwrap_or(&[]),
190 count,
191 };
192 for (name, _) in params.iter() {
194 if let (base, Section::Part { n: 0, .. }) = split_name(name) {
195 let c = Continuation { params, name: base };
196 let _ = c.walk(limits, &mut |_| {})?;
197 } else if let (base, Section::Part { .. }) = split_name(name) {
198 let has_zero = params
199 .iter()
200 .any(|(n, _)| matches!(split_name(n), (b, Section::Part { n: 0, .. }) if b.eq_ignore_ascii_case(base)));
201 if !has_zero {
202 return err(ErrorKind::SectionGap, 0);
203 }
204 }
205 }
206 Ok((params, i))
207}
208
209#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
222pub struct MimeParams<'a> {
223 text: &'a [u8],
224 count: usize,
225}
226
227impl<'a> MimeParams<'a> {
228 #[must_use]
243 pub fn get(&self, name: &str) -> Option<MimeValue<'a>> {
244 let want = name.as_bytes();
245 let mut ext = None;
246 let mut plain = None;
247 for (n, w) in self.iter() {
248 match split_name(n) {
249 (b, Section::Part { n: 0, .. }) if b.eq_ignore_ascii_case(want) => {
250 return Some(MimeValue::Continued(Continuation {
251 params: *self,
252 name: b,
253 }));
254 }
255 (b, Section::Ext) if b.eq_ignore_ascii_case(want) => {
256 if let Word::Token(t) = w {
257 ext = ExtValue::parse(t).ok();
258 }
259 }
260 (b, Section::Plain) if b.eq_ignore_ascii_case(want) => plain = Some(w),
261 _ => {}
262 }
263 }
264 ext.map(MimeValue::Extended).or(plain.map(MimeValue::Plain))
265 }
266
267 pub fn decode_into<'b>(&self, name: &str, out: &'b mut [u8]) -> Result<Option<&'b str>> {
288 match self.get(name) {
289 None => Ok(None),
290 Some(MimeValue::Continued(c)) => c.decode_into(out).map(Some),
291 Some(MimeValue::Extended(e)) => e.decode_into(out).map(Some),
292 Some(MimeValue::Plain(w)) => {
293 let len = w.len();
294 let Some(dst) = out.get_mut(..len) else {
295 return err(ErrorKind::BufferTooSmall, 0);
296 };
297 let utf8 = core::str::from_utf8(w.unescape_into(dst)?).is_ok();
298 if !utf8 {
299 dst.fill(0);
300 return err(ErrorKind::InvalidUtf8, 0);
301 }
302 core::str::from_utf8(dst)
303 .map(Some)
304 .map_err(|e| ParseError::new(ErrorKind::InvalidUtf8, e.valid_up_to()))
305 }
306 }
307 }
308
309 #[must_use]
322 pub fn iter(&self) -> MimeParamIter<'a> {
323 MimeParamIter { s: self.text, i: 0 }
324 }
325
326 #[must_use]
338 pub const fn len(&self) -> usize {
339 self.count
340 }
341
342 #[must_use]
354 pub const fn is_empty(&self) -> bool {
355 self.count == 0
356 }
357}
358
359#[derive(Clone, Debug)]
374pub struct MimeParamIter<'a> {
375 s: &'a [u8],
376 i: usize,
377}
378
379impl<'a> Iterator for MimeParamIter<'a> {
380 type Item = (&'a [u8], Word<'a>);
381
382 fn next(&mut self) -> Option<Self::Item> {
383 let j = cfws(self.s, self.i, &RELAXED).ok()?;
384 if self.s.get(j) != Some(&b';') {
385 return None;
386 }
387 let (name, value, _, end) = param_at(self.s, j, &RELAXED).ok()?;
388 self.i = end;
389 Some((name, value))
390 }
391}
392
393#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
406pub enum MimeValue<'a> {
407 Plain(Word<'a>),
409 Extended(ExtValue<'a>),
411 Continued(Continuation<'a>),
413}
414
415#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
429pub struct Continuation<'a> {
430 params: MimeParams<'a>,
431 name: &'a [u8],
432}
433
434impl Continuation<'_> {
435 #[must_use]
448 pub fn sections(&self) -> usize {
449 self.params
450 .iter()
451 .filter(|(n, _)| matches!(split_name(n), (b, Section::Part { .. }) if b.eq_ignore_ascii_case(self.name)))
452 .count()
453 }
454
455 #[must_use]
471 pub fn max_decoded_len(&self) -> usize {
472 self.params
473 .iter()
474 .filter(|(n, _)| matches!(split_name(n), (b, Section::Part { .. }) if b.eq_ignore_ascii_case(self.name)))
475 .map(|(_, w)| w.len().saturating_mul(2))
476 .sum()
477 }
478
479 pub fn decode_into<'b>(&self, out: &'b mut [u8]) -> Result<&'b str> {
498 let len = self.walk(&RELAXED, &mut |_| {})?;
499 let Some(dst) = out.get_mut(..len) else {
500 return err(ErrorKind::BufferTooSmall, 0);
501 };
502 let mut n = 0usize;
503 let _ = self.walk(&RELAXED, &mut |b: u8| {
504 if let Some(d) = dst.get_mut(n) {
505 *d = b;
506 }
507 n += 1;
508 })?;
509 core::str::from_utf8(dst)
510 .map_err(|e| ParseError::new(ErrorKind::InvalidUtf8, e.valid_up_to()))
511 }
512
513 fn walk(&self, limits: &Limits, emit: &mut impl FnMut(u8)) -> Result<usize> {
516 let mut slots: [Option<(bool, Word<'_>)>; MAX_RFC2231_SECTIONS as usize] =
517 [None; MAX_RFC2231_SECTIONS as usize];
518 let max = usize::from(limits.rfc2231_sections.min(MAX_RFC2231_SECTIONS));
519 let mut highest = 0usize;
520 for (name, w) in self.params.iter() {
521 if let (b, Section::Part { n, ext }) = split_name(name) {
522 if !b.eq_ignore_ascii_case(self.name) {
523 continue;
524 }
525 if n >= max {
526 return err(ErrorKind::TooManySections, 0);
527 }
528 if let Some(slot) = slots.get_mut(n) {
529 *slot = Some((ext, w));
530 }
531 highest = highest.max(n);
532 }
533 }
534 let used = slots.get(..=highest).unwrap_or(&[]);
535 if used.iter().any(Option::is_none) {
536 return err(ErrorKind::SectionGap, 0);
537 }
538 let mut charset = Charset::Utf8;
539 let mut utf8 = crate::utf8::Utf8::new();
540 let mut count = 0usize;
541 let mut put = |b: u8, charset: Charset, count: &mut usize| -> Result<()> {
542 let decoded = [0xc0 | (b >> 6), 0x80 | (b & 0x3f)];
543 let bytes: &[u8] = if charset == Charset::Iso8859_1 && b >= 0x80 {
544 &decoded
545 } else {
546 core::slice::from_ref(&b)
547 };
548 for &x in bytes {
549 if !utf8.push(x) {
550 return err(ErrorKind::InvalidUtf8, 0);
551 }
552 emit(x);
553 *count += 1;
554 }
555 Ok(())
556 };
557 for (k, slot) in used.iter().enumerate() {
558 let Some((ext, w)) = *slot else { continue };
559 let raw: &[u8] = match (k, ext, w) {
560 (0, true, Word::Token(t)) => {
561 let e = ExtValue::parse(t)?;
562 charset = e.charset();
563 e.encoded()
564 }
565 (_, true, Word::Token(t)) => t,
566 _ => &[],
567 };
568 if ext {
569 let mut i = 0usize;
570 while let Some(&b) = raw.get(i) {
571 let byte = if b == b'%' {
572 let hi = raw
573 .get(i + 1)
574 .copied()
575 .and_then(class::hex_value)
576 .unwrap_or(0);
577 let lo = raw
578 .get(i + 2)
579 .copied()
580 .and_then(class::hex_value)
581 .unwrap_or(0);
582 i += 3;
583 (hi << 4) | lo
584 } else {
585 i += 1;
586 b
587 };
588 if byte < 0x20 || byte == 0x7f {
589 return err(ErrorKind::ControlInDecoded, 0);
590 }
591 if charset == Charset::UsAscii && byte >= 0x80 {
592 return err(ErrorKind::InvalidByte, 0);
593 }
594 put(byte, charset, &mut count)?;
595 }
596 } else {
597 match w {
598 Word::Token(t) => {
599 for &byte in t {
600 put(byte, charset, &mut count)?;
601 }
602 }
603 Word::Quoted(q) => {
604 for byte in q.bytes() {
605 put(byte, charset, &mut count)?;
606 }
607 }
608 }
609 }
610 }
611 if !utf8.is_complete() {
612 return err(ErrorKind::InvalidUtf8, 0);
613 }
614 Ok(count)
615 }
616}
617
618fn is_attribute(name: &[u8]) -> bool {
621 !name.is_empty()
622 && name
623 .iter()
624 .all(|&b| class::is_mime_token_char(b) && !matches!(b, b'*' | b'\'' | b'%'))
625}
626
627const LINE: usize = 78;
629
630const fn unit_len(b: u8) -> usize {
632 if class::is_attr_char(b) { 1 } else { 3 }
633}
634
635fn put_unit(b: u8, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
637 const HEX: &[u8; 16] = b"0123456789ABCDEF";
638 if class::is_attr_char(b) {
639 out.put(&[b])?;
640 } else {
641 let hi = HEX.get(usize::from(b >> 4)).copied().unwrap_or(b'0');
642 let lo = HEX.get(usize::from(b & 0xf)).copied().unwrap_or(b'0');
643 out.put(&[b'%', hi, lo])?;
644 }
645 Ok(())
646}
647
648fn put_param(name: &str, value: &str, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
649 let (n, v) = (name.as_bytes(), value.as_bytes());
650 if !v.is_empty()
651 && v.iter().all(|&b| class::is_mime_token_char(b))
652 && 3 + n.len() + v.len() <= LINE
653 {
654 out.put(b"; ")?;
655 out.put(n)?;
656 out.put(b"=")?;
657 out.put(v)?;
658 return Ok(());
659 }
660 let escaped = v.iter().filter(|&&b| b == b'"' || b == b'\\').count();
661 if v.iter().all(|&b| (0x20..=0x7e).contains(&b)) && 5 + n.len() + v.len() + escaped <= LINE {
662 out.put(b"; ")?;
663 out.put(n)?;
664 out.put(b"=\"")?;
665 for &b in v {
666 if b == b'"' || b == b'\\' {
667 out.put(b"\\")?;
668 }
669 out.put(&[b])?;
670 }
671 out.put(b"\"")?;
672 return Ok(());
673 }
674 let encoded: usize = v.iter().map(|&b| unit_len(b)).sum();
675 if 4 + n.len() + 7 + encoded <= LINE {
676 out.put(b"; ")?;
677 out.put(n)?;
678 out.put(b"*=UTF-8''")?;
679 for &b in v {
680 put_unit(b, out)?;
681 }
682 return Ok(());
683 }
684 out.put(b";")?;
687 let mut rest = v;
688 let mut k = 0usize;
689 while !rest.is_empty() || k == 0 {
690 if k >= usize::from(MAX_RFC2231_SECTIONS) {
691 return Err(WriteError::OutOfRange);
692 }
693 let mut digits = [0u8; 2];
694 let dl = if k < 10 {
695 digits[0] = b'0' + k as u8;
696 1
697 } else {
698 digits[0] = b'0' + (k / 10) as u8;
699 digits[1] = b'0' + (k % 10) as u8;
700 2
701 };
702 if k > 0 {
703 out.put(b";")?;
704 }
705 out.put(b"\r\n ")?;
706 out.put(n)?;
707 out.put(b"*")?;
708 out.put(digits.get(..dl).unwrap_or(&[]))?;
709 out.put(b"*=")?;
710 let mut col = 1 + n.len() + 1 + dl + 2;
711 if k == 0 {
712 out.put(b"UTF-8''")?;
713 col += 7;
714 }
715 let mut took = 0usize;
716 for &b in rest {
717 let u = unit_len(b);
718 if col + u + 1 > LINE && took > 0 {
720 break;
721 }
722 put_unit(b, out)?;
723 col += u;
724 took += 1;
725 }
726 rest = rest.get(took..).unwrap_or(&[]);
727 k += 1;
728 }
729 Ok(())
730}
731
732pub fn write_mime_param(
757 name: &str,
758 value: &str,
759 out: &mut impl Sink,
760) -> core::result::Result<(), WriteError> {
761 if !is_attribute(name.as_bytes()) {
762 return Err(WriteError::NotToken);
763 }
764 if let Some((at, _)) = value.char_indices().find(|&(_, c)| c.is_control()) {
765 return Err(WriteError::forbidden(at));
766 }
767 let mut count = CountSink::new();
768 put_param(name, value, &mut count)?;
769 reserve(out, count.count())?;
770 put_param(name, value, out)
771}
772
773#[cfg(test)]
774mod tests {
775 use super::*;
776 use std::{format, string::String, vec, vec::Vec};
777
778 fn p(v: &[u8]) -> MimeParams<'_> {
779 parse_mime_params(v, &Limits::DEFAULT).unwrap().0
780 }
781
782 fn k(v: &[u8]) -> ErrorKind {
783 parse_mime_params(v, &Limits::DEFAULT).unwrap_err().kind()
784 }
785
786 fn get(v: &[u8], name: &str) -> String {
787 let mut buf = vec![0u8; 512];
788 String::from(p(v).decode_into(name, &mut buf).unwrap().unwrap())
789 }
790
791 #[test]
792 fn test_mime_params_basic() {
793 assert_eq!(get(b"; a=b; c=\"d e\"", "C"), "d e");
794 assert_eq!(get(b"; boundary={x}", "boundary"), "{x}");
795 assert_eq!(get(b" ; a = (c) b (d)", "a"), "b");
796 assert_eq!(get(b";\r\n a=b", "a"), "b");
797 assert!(
798 p(b"; a=b")
799 .decode_into("z", &mut [0u8; 4])
800 .unwrap()
801 .is_none()
802 );
803 assert_eq!(parse_mime_params(b"", &Limits::DEFAULT).unwrap().1, 0);
804 assert_eq!(
805 parse_mime_params(b"; a=b x", &Limits::DEFAULT).unwrap().1,
806 5
807 );
808 assert_eq!(k(b"; =b"), ErrorKind::NotToken);
809 assert_eq!(k(b"; a"), ErrorKind::MissingValue);
810 assert_eq!(k(b"; a="), ErrorKind::MissingValue);
811 assert_eq!(k(b"; a=\"b"), ErrorKind::UnterminatedQuote);
812 assert_eq!(k(b"; a=1; A=2"), ErrorKind::DuplicateParameter);
813 assert_eq!(k(b"; a=1 (x"), ErrorKind::UnbalancedComment);
814 let l = Limits::DEFAULT.max_params(2);
815 assert!(parse_mime_params(b"; a=1; b=2", &l).is_ok());
816 assert_eq!(
817 parse_mime_params(b"; a=1; b=2; c=3", &l)
818 .unwrap_err()
819 .kind(),
820 ErrorKind::TooManyMembers
821 );
822 }
823
824 #[test]
825 fn test_mime_params_rfc2231() {
826 assert_eq!(
827 get(
828 b"; t*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A",
829 "t"
830 ),
831 "This is ***fun***"
832 );
833 assert_eq!(get(b"; t*0=\"foo\"; t*1=bar", "t"), "foobar");
834 assert_eq!(get(b"; t*1=bar; t*0=\"foo\"", "t"), "foobar");
835 assert_eq!(get(b"; t*0*=UTF-8''%E2%82; t*1*=%AC", "t"), "\u{20ac}");
836 assert_eq!(get(b"; t=plain; t*=UTF-8''ext", "t"), "ext");
837 assert_eq!(get(b"; t=plain; t*0=cont", "t"), "cont");
838 assert_eq!(get(b"; t*00=x; t=plain", "t"), "plain");
839 assert_eq!(k(b"; t*0=a; t*2=c"), ErrorKind::SectionGap);
840 assert_eq!(k(b"; t*1=a"), ErrorKind::SectionGap);
841 assert_eq!(k(b"; t*0=a; t*0=b"), ErrorKind::DuplicateParameter);
842 assert_eq!(
843 k(b"; t*0*=UTF-8''a; t*1*=UTF-8''b"),
844 ErrorKind::InvalidPercentEncoding
845 );
846 assert_eq!(k(b"; t*0*=UTF-8''%C3"), ErrorKind::InvalidUtf8);
847 assert_eq!(k(b"; t*0*=UTF-8''%0D"), ErrorKind::ControlInDecoded);
848 assert_eq!(k(b"; t*=\"UTF-8''x\""), ErrorKind::InvalidCharset);
849 let mut v = Vec::new();
850 for i in 0..32 {
851 v.extend_from_slice(format!("; t*{i}=x").as_bytes());
852 }
853 assert_eq!(
854 p(&v)
855 .get("t")
856 .map(|m| matches!(m, MimeValue::Continued(c) if c.sections() == 32)),
857 Some(true)
858 );
859 v.extend_from_slice(b"; t*32=x");
860 assert_eq!(k(&v), ErrorKind::TooManySections);
861 let l = Limits::DEFAULT.max_rfc2231_sections(2);
862 assert!(parse_mime_params(b"; t*0=a; t*1=b", &l).is_ok());
863 assert_eq!(
864 parse_mime_params(b"; t*0=a; t*1=b; t*2=c", &l)
865 .unwrap_err()
866 .kind(),
867 ErrorKind::TooManySections
868 );
869 }
870
871 fn written(name: &str, value: &str) -> Vec<u8> {
872 let mut buf = vec![0u8; 4096];
873 let mut out = crate::SliceSink::new(&mut buf);
874 write_mime_param(name, value, &mut out).unwrap();
875 out.written().to_vec()
876 }
877
878 #[test]
879 fn test_write_mime_param_forms_and_round_trip() {
880 assert_eq!(written("charset", "utf-8"), b"; charset=utf-8");
881 assert_eq!(written("name", "a \"b\""), b"; name=\"a \\\"b\\\"\"");
882 assert_eq!(written("name", ""), b"; name=\"\"");
883 let long = "x".repeat(200);
884 let nonascii = "\u{e9}".repeat(60);
885 for value in [
886 String::from("plain"),
887 long,
888 nonascii,
889 String::from("caf\u{e9} au lait.txt"),
890 ] {
891 let w = written("filename", &value);
892 for line in w.split(|&b| b == b'\n') {
893 assert!(line.len() <= 79, "{}", String::from_utf8_lossy(line));
894 }
895 let (params, used) = parse_mime_params(&w, &Limits::DEFAULT).unwrap();
896 assert_eq!(used, w.len());
897 let mut buf = vec![0u8; 1024];
898 assert_eq!(
899 params.decode_into("filename", &mut buf).unwrap(),
900 Some(value.as_str())
901 );
902 }
903 let mut buf = [0u8; 8];
904 let mut out = crate::SliceSink::new(&mut buf);
905 assert_eq!(
906 write_mime_param("a*b", "x", &mut out),
907 Err(WriteError::NotToken)
908 );
909 assert_eq!(
910 write_mime_param("a", "x\ny", &mut out),
911 Err(WriteError::Forbidden { at: 1 })
912 );
913 assert_eq!(
914 write_mime_param("a", "toolongvalue", &mut out),
915 Err(WriteError::Full)
916 );
917 assert!(out.is_empty());
918 let mut big = vec![0u8; 1 << 16];
919 let mut out = crate::SliceSink::new(&mut big);
920 assert_eq!(
921 write_mime_param("f", &"\u{e9}".repeat(1000), &mut out),
922 Err(WriteError::OutOfRange)
923 );
924 }
925}