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 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.len() <= 3
42 && digits.iter().all(u8::is_ascii_digit)
43 && (digits.len() == 1 || digits.first() != Some(&b'0'));
44 if canonical {
45 let n = digits
46 .iter()
47 .fold(0usize, |a, &d| a * 10 + usize::from(d - b'0'));
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 class::is_attr_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(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 return err(ErrorKind::SectionGap, 0);
204 }
205 }
206 }
207 Ok((params, i))
208}
209
210#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
223pub struct MimeParams<'a> {
224 text: &'a [u8],
225 count: usize,
226}
227
228impl<'a> MimeParams<'a> {
229 #[must_use]
244 pub fn get(&self, name: &str) -> Option<MimeValue<'a>> {
245 let want = name.as_bytes();
246 let mut ext = None;
247 let mut plain = None;
248 for (n, w) in self.iter() {
249 match split_name(n) {
250 (b, Section::Part { n: 0, .. }) if b.eq_ignore_ascii_case(want) => {
251 return Some(MimeValue::Continued(Continuation {
252 params: *self,
253 name: b,
254 }));
255 }
256 (b, Section::Ext) if b.eq_ignore_ascii_case(want) => {
257 if let Word::Token(t) = w {
258 ext = ExtValue::parse(t).ok();
259 }
260 }
261 (b, Section::Plain) if b.eq_ignore_ascii_case(want) => plain = Some(w),
262 _ => {}
263 }
264 }
265 ext.map(MimeValue::Extended).or(plain.map(MimeValue::Plain))
266 }
267
268 pub fn decode_into<'b>(&self, name: &str, out: &'b mut [u8]) -> Result<Option<&'b str>> {
289 match self.get(name) {
290 None => Ok(None),
291 Some(MimeValue::Continued(c)) => c.decode_into(out).map(Some),
292 Some(MimeValue::Extended(e)) => e.decode_into(out).map(Some),
293 Some(MimeValue::Plain(w)) => {
294 let len = w.len();
295 let Some(dst) = out.get_mut(..len) else {
296 return err(ErrorKind::BufferTooSmall, 0);
297 };
298 let utf8 = core::str::from_utf8(w.unescape_into(dst)?).is_ok();
299 if !utf8 {
300 dst.fill(0);
301 return err(ErrorKind::InvalidUtf8, 0);
302 }
303 core::str::from_utf8(dst)
304 .map(Some)
305 .map_err(|e| ParseError::new(ErrorKind::InvalidUtf8, e.valid_up_to()))
306 }
307 }
308 }
309
310 #[must_use]
323 pub fn iter(&self) -> MimeParamIter<'a> {
324 MimeParamIter { s: self.text, i: 0 }
325 }
326
327 #[must_use]
339 pub const fn len(&self) -> usize {
340 self.count
341 }
342
343 #[must_use]
355 pub const fn is_empty(&self) -> bool {
356 self.count == 0
357 }
358}
359
360#[derive(Clone, Debug)]
375pub struct MimeParamIter<'a> {
376 s: &'a [u8],
377 i: usize,
378}
379
380impl<'a> Iterator for MimeParamIter<'a> {
381 type Item = (&'a [u8], Word<'a>);
382
383 fn next(&mut self) -> Option<Self::Item> {
384 let j = cfws(self.s, self.i, &RELAXED).ok()?;
385 if self.s.get(j) != Some(&b';') {
386 return None;
387 }
388 let (name, value, _, end) = param_at(self.s, j, &RELAXED).ok()?;
389 self.i = end;
390 Some((name, value))
391 }
392}
393
394#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
407pub enum MimeValue<'a> {
408 Plain(Word<'a>),
410 Extended(ExtValue<'a>),
412 Continued(Continuation<'a>),
414}
415
416#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
430pub struct Continuation<'a> {
431 params: MimeParams<'a>,
432 name: &'a [u8],
433}
434
435impl Continuation<'_> {
436 #[must_use]
449 pub fn sections(&self) -> usize {
450 self.params
451 .iter()
452 .filter(|(n, _)| matches!(split_name(n), (b, Section::Part { .. }) if b.eq_ignore_ascii_case(self.name)))
453 .count()
454 }
455
456 #[must_use]
472 pub fn max_decoded_len(&self) -> usize {
473 self.params
474 .iter()
475 .filter(|(n, _)| matches!(split_name(n), (b, Section::Part { .. }) if b.eq_ignore_ascii_case(self.name)))
476 .map(|(_, w)| w.len().saturating_mul(2))
477 .sum()
478 }
479
480 pub fn decode_into<'b>(&self, out: &'b mut [u8]) -> Result<&'b str> {
499 let len = self.walk(&RELAXED, &mut |_| {})?;
500 let Some(dst) = out.get_mut(..len) else {
501 return err(ErrorKind::BufferTooSmall, 0);
502 };
503 let mut n = 0usize;
504 let _ = self.walk(&RELAXED, &mut |b: u8| {
505 if let Some(d) = dst.get_mut(n) {
506 *d = b;
507 }
508 n += 1;
509 })?;
510 core::str::from_utf8(dst)
511 .map_err(|e| ParseError::new(ErrorKind::InvalidUtf8, e.valid_up_to()))
512 }
513
514 fn walk(&self, limits: &Limits, emit: &mut impl FnMut(u8)) -> Result<usize> {
517 let mut slots: [Option<(bool, Word<'_>)>; MAX_RFC2231_SECTIONS as usize] =
518 [None; MAX_RFC2231_SECTIONS as usize];
519 let max = usize::from(limits.rfc2231_sections.min(MAX_RFC2231_SECTIONS));
520 let mut highest = 0usize;
521 for (name, w) in self.params.iter() {
522 if let (b, Section::Part { n, ext }) = split_name(name) {
523 if !b.eq_ignore_ascii_case(self.name) {
524 continue;
525 }
526 if n >= max {
527 return err(ErrorKind::TooManySections, 0);
528 }
529 if let Some(slot) = slots.get_mut(n) {
530 *slot = Some((ext, w));
531 }
532 highest = highest.max(n);
533 }
534 }
535 let used = slots.get(..=highest).unwrap_or(&[]);
536 if used.iter().any(Option::is_none) {
537 return err(ErrorKind::SectionGap, 0);
538 }
539 let mut charset = Charset::Utf8;
540 let mut utf8 = crate::utf8::Utf8::new();
541 let mut count = 0usize;
542 let mut put = |b: u8, charset: Charset, count: &mut usize| -> Result<()> {
543 let decoded = [0xc0 | (b >> 6), 0x80 | (b & 0x3f)];
544 let bytes: &[u8] = if charset == Charset::Iso8859_1 && b >= 0x80 {
545 &decoded
546 } else {
547 core::slice::from_ref(&b)
548 };
549 for &x in bytes {
550 if !utf8.push(x) {
551 return err(ErrorKind::InvalidUtf8, 0);
552 }
553 emit(x);
554 *count += 1;
555 }
556 Ok(())
557 };
558 for (k, slot) in used.iter().enumerate() {
559 let Some((ext, w)) = *slot else { continue };
560 let raw: &[u8] = match (k, ext, w) {
561 (0, true, Word::Token(t)) => {
562 let e = ExtValue::parse(t)?;
563 charset = e.charset();
564 e.encoded()
565 }
566 (_, true, Word::Token(t)) => t,
567 _ => &[],
568 };
569 if ext {
570 let mut i = 0usize;
571 while let Some(&b) = raw.get(i) {
572 let byte = if b == b'%' {
573 let hi = raw
574 .get(i + 1)
575 .copied()
576 .and_then(class::hex_value)
577 .unwrap_or(0);
578 let lo = raw
579 .get(i + 2)
580 .copied()
581 .and_then(class::hex_value)
582 .unwrap_or(0);
583 i += 3;
584 (hi << 4) | lo
585 } else {
586 i += 1;
587 b
588 };
589 if byte < 0x20 || byte == 0x7f {
590 return err(ErrorKind::ControlInDecoded, 0);
591 }
592 if charset == Charset::UsAscii && byte >= 0x80 {
593 return err(ErrorKind::InvalidByte, 0);
594 }
595 put(byte, charset, &mut count)?;
596 }
597 } else {
598 match w {
599 Word::Token(t) => {
600 for &byte in t {
601 put(byte, charset, &mut count)?;
602 }
603 }
604 Word::Quoted(q) => {
605 for byte in q.bytes() {
606 put(byte, charset, &mut count)?;
607 }
608 }
609 }
610 }
611 }
612 if !utf8.is_complete() {
613 return err(ErrorKind::InvalidUtf8, 0);
614 }
615 Ok(count)
616 }
617}
618
619fn is_attribute(name: &[u8]) -> bool {
622 !name.is_empty()
623 && name
624 .iter()
625 .all(|&b| class::is_mime_token_char(b) && !matches!(b, b'*' | b'\'' | b'%'))
626}
627
628const LINE: usize = 78;
630
631const fn unit_len(b: u8) -> usize {
633 if class::is_attr_char(b) { 1 } else { 3 }
634}
635
636fn put_unit(b: u8, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
638 const HEX: &[u8; 16] = b"0123456789ABCDEF";
639 if class::is_attr_char(b) {
640 out.put(&[b])?;
641 } else {
642 let hi = HEX.get(usize::from(b >> 4)).copied().unwrap_or(b'0');
643 let lo = HEX.get(usize::from(b & 0xf)).copied().unwrap_or(b'0');
644 out.put(&[b'%', hi, lo])?;
645 }
646 Ok(())
647}
648
649fn put_param(name: &str, value: &str, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
650 let (n, v) = (name.as_bytes(), value.as_bytes());
651 if !v.is_empty()
652 && v.iter().all(|&b| class::is_mime_token_char(b))
653 && 3 + n.len() + v.len() <= LINE
654 {
655 out.put(b"; ")?;
656 out.put(n)?;
657 out.put(b"=")?;
658 out.put(v)?;
659 return Ok(());
660 }
661 let escaped = v.iter().filter(|&&b| b == b'"' || b == b'\\').count();
662 if v.iter().all(|&b| (0x20..=0x7e).contains(&b)) && 5 + n.len() + v.len() + escaped <= LINE {
663 out.put(b"; ")?;
664 out.put(n)?;
665 out.put(b"=\"")?;
666 for &b in v {
667 if b == b'"' || b == b'\\' {
668 out.put(b"\\")?;
669 }
670 out.put(&[b])?;
671 }
672 out.put(b"\"")?;
673 return Ok(());
674 }
675 let encoded: usize = v.iter().map(|&b| unit_len(b)).sum();
676 if 4 + n.len() + 7 + encoded <= LINE {
677 out.put(b"; ")?;
678 out.put(n)?;
679 out.put(b"*=UTF-8''")?;
680 for &b in v {
681 put_unit(b, out)?;
682 }
683 return Ok(());
684 }
685 out.put(b";")?;
688 let mut rest = v;
689 let mut k = 0usize;
690 while !rest.is_empty() || k == 0 {
691 if k >= usize::from(MAX_RFC2231_SECTIONS) {
692 return Err(WriteError::OutOfRange);
693 }
694 let mut digits = [0u8; 2];
695 let dl = if k < 10 {
696 digits[0] = b'0' + k as u8;
697 1
698 } else {
699 digits[0] = b'0' + (k / 10) as u8;
700 digits[1] = b'0' + (k % 10) as u8;
701 2
702 };
703 if k > 0 {
704 out.put(b";")?;
705 }
706 out.put(b"\r\n ")?;
707 out.put(n)?;
708 out.put(b"*")?;
709 out.put(digits.get(..dl).unwrap_or(&[]))?;
710 out.put(b"*=")?;
711 let mut col = 1 + n.len() + 1 + dl + 2;
712 if k == 0 {
713 out.put(b"UTF-8''")?;
714 col += 7;
715 }
716 let mut took = 0usize;
717 for &b in rest {
718 let u = unit_len(b);
719 if col + u + 1 > LINE && took > 0 {
721 break;
722 }
723 put_unit(b, out)?;
724 col += u;
725 took += 1;
726 }
727 rest = rest.get(took..).unwrap_or(&[]);
728 k += 1;
729 }
730 Ok(())
731}
732
733pub fn write_mime_param(
758 name: &str,
759 value: &str,
760 out: &mut impl Sink,
761) -> core::result::Result<(), WriteError> {
762 if !is_attribute(name.as_bytes()) {
763 return Err(WriteError::NotToken);
764 }
765 if let Some((at, _)) = value.char_indices().find(|&(_, c)| c.is_control()) {
766 return Err(WriteError::forbidden(at));
767 }
768 let mut count = CountSink::new();
769 put_param(name, value, &mut count)?;
770 reserve(out, count.count())?;
771 put_param(name, value, out)
772}
773
774#[cfg(test)]
775mod tests {
776 use super::*;
777 use std::{format, string::String, vec, vec::Vec};
778
779 fn p(v: &[u8]) -> MimeParams<'_> {
780 parse_mime_params(v, &Limits::DEFAULT).unwrap().0
781 }
782
783 fn k(v: &[u8]) -> ErrorKind {
784 parse_mime_params(v, &Limits::DEFAULT).unwrap_err().kind()
785 }
786
787 fn get(v: &[u8], name: &str) -> String {
788 let mut buf = vec![0u8; 512];
789 String::from(p(v).decode_into(name, &mut buf).unwrap().unwrap())
790 }
791
792 #[test]
793 fn test_mime_params_basic() {
794 assert_eq!(get(b"; a=b; c=\"d e\"", "C"), "d e");
795 assert_eq!(get(b"; boundary={x}", "boundary"), "{x}");
796 assert_eq!(get(b" ; a = (c) b (d)", "a"), "b");
797 assert_eq!(get(b";\r\n a=b", "a"), "b");
798 assert!(
799 p(b"; a=b")
800 .decode_into("z", &mut [0u8; 4])
801 .unwrap()
802 .is_none()
803 );
804 assert_eq!(parse_mime_params(b"", &Limits::DEFAULT).unwrap().1, 0);
805 assert_eq!(
806 parse_mime_params(b"; a=b x", &Limits::DEFAULT).unwrap().1,
807 5
808 );
809 assert_eq!(k(b"; =b"), ErrorKind::NotToken);
810 assert_eq!(k(b"; a"), ErrorKind::MissingValue);
811 assert_eq!(k(b"; a="), ErrorKind::MissingValue);
812 assert_eq!(k(b"; a=\"b"), ErrorKind::UnterminatedQuote);
813 assert_eq!(k(b"; a=1; A=2"), ErrorKind::DuplicateParameter);
814 assert_eq!(k(b"; a=1 (x"), ErrorKind::UnbalancedComment);
815 let l = Limits::DEFAULT.max_params(2);
816 assert!(parse_mime_params(b"; a=1; b=2", &l).is_ok());
817 assert_eq!(
818 parse_mime_params(b"; a=1; b=2; c=3", &l)
819 .unwrap_err()
820 .kind(),
821 ErrorKind::TooManyMembers
822 );
823 }
824
825 #[test]
826 fn test_mime_params_rfc2231() {
827 assert_eq!(
828 get(
829 b"; t*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A",
830 "t"
831 ),
832 "This is ***fun***"
833 );
834 assert_eq!(get(b"; t*0=\"foo\"; t*1=bar", "t"), "foobar");
835 assert_eq!(get(b"; t*1=bar; t*0=\"foo\"", "t"), "foobar");
836 assert_eq!(get(b"; t*0*=UTF-8''%E2%82; t*1*=%AC", "t"), "\u{20ac}");
837 assert_eq!(get(b"; t=plain; t*=UTF-8''ext", "t"), "ext");
838 assert_eq!(get(b"; t=plain; t*0=cont", "t"), "cont");
839 assert_eq!(get(b"; t*00=x; t=plain", "t"), "plain");
840 assert_eq!(k(b"; t*0=a; t*2=c"), ErrorKind::SectionGap);
841 assert_eq!(k(b"; t*1=a"), ErrorKind::SectionGap);
842 assert_eq!(k(b"; t*0=a; t*0=b"), ErrorKind::DuplicateParameter);
843 assert_eq!(
844 k(b"; t*0*=UTF-8''a; t*1*=UTF-8''b"),
845 ErrorKind::InvalidPercentEncoding
846 );
847 assert_eq!(k(b"; t*0*=UTF-8''%C3"), ErrorKind::InvalidUtf8);
848 assert_eq!(k(b"; t*0*=UTF-8''%0D"), ErrorKind::ControlInDecoded);
849 assert_eq!(k(b"; t*=\"UTF-8''x\""), ErrorKind::InvalidCharset);
850 let mut v = Vec::new();
851 for i in 0..32 {
852 v.extend_from_slice(format!("; t*{i}=x").as_bytes());
853 }
854 assert_eq!(
855 p(&v)
856 .get("t")
857 .map(|m| matches!(m, MimeValue::Continued(c) if c.sections() == 32)),
858 Some(true)
859 );
860 v.extend_from_slice(b"; t*32=x");
861 assert_eq!(k(&v), ErrorKind::TooManySections);
862 let l = Limits::DEFAULT.max_rfc2231_sections(2);
863 assert!(parse_mime_params(b"; t*0=a; t*1=b", &l).is_ok());
864 assert_eq!(
865 parse_mime_params(b"; t*0=a; t*1=b; t*2=c", &l)
866 .unwrap_err()
867 .kind(),
868 ErrorKind::TooManySections
869 );
870 }
871
872 fn written(name: &str, value: &str) -> Vec<u8> {
873 let mut buf = vec![0u8; 4096];
874 let mut out = crate::SliceSink::new(&mut buf);
875 write_mime_param(name, value, &mut out).unwrap();
876 out.written().to_vec()
877 }
878
879 #[test]
880 fn test_write_mime_param_forms_and_round_trip() {
881 assert_eq!(written("charset", "utf-8"), b"; charset=utf-8");
882 assert_eq!(written("name", "a \"b\""), b"; name=\"a \\\"b\\\"\"");
883 assert_eq!(written("name", ""), b"; name=\"\"");
884 let long = "x".repeat(200);
885 let nonascii = "\u{e9}".repeat(60);
886 for value in [
887 String::from("plain"),
888 long,
889 nonascii,
890 String::from("caf\u{e9} au lait.txt"),
891 ] {
892 let w = written("filename", &value);
893 for line in w.split(|&b| b == b'\n') {
894 assert!(line.len() <= 79, "{}", String::from_utf8_lossy(line));
895 }
896 let (params, used) = parse_mime_params(&w, &Limits::DEFAULT).unwrap();
897 assert_eq!(used, w.len());
898 let mut buf = vec![0u8; 1024];
899 assert_eq!(
900 params.decode_into("filename", &mut buf).unwrap(),
901 Some(value.as_str())
902 );
903 }
904 let mut buf = [0u8; 8];
905 let mut out = crate::SliceSink::new(&mut buf);
906 assert_eq!(
907 write_mime_param("a*b", "x", &mut out),
908 Err(WriteError::NotToken)
909 );
910 assert_eq!(
911 write_mime_param("a", "x\ny", &mut out),
912 Err(WriteError::Forbidden { at: 1 })
913 );
914 assert_eq!(
915 write_mime_param("a", "toolongvalue", &mut out),
916 Err(WriteError::Full)
917 );
918 assert!(out.is_empty());
919 let mut big = vec![0u8; 1 << 16];
920 let mut out = crate::SliceSink::new(&mut big);
921 assert_eq!(
922 write_mime_param("f", &"\u{e9}".repeat(1000), &mut out),
923 Err(WriteError::OutOfRange)
924 );
925 }
926}