1#![doc(html_root_url = "https://docs.rs/mime/0.3.17")]
27#![deny(warnings)]
28#![deny(missing_docs)]
29#![deny(missing_debug_implementations)]
30
31
32use std::cmp::Ordering;
33use std::error::Error;
34use std::fmt;
35use std::hash::{Hash, Hasher};
36use std::str::FromStr;
37use std::slice;
38
39mod parse;
40
41#[derive(Clone)]
43pub struct Mime {
44    source: Source,
45    slash: usize,
46    plus: Option<usize>,
47    params: ParamSource,
48}
49
50#[derive(Clone, Debug)]
52pub struct MimeIter<'a> {
53    pos: usize,
54    source: &'a str,
55}
56
57#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub struct Name<'a> {
65    source: &'a str,
74    insensitive: bool,
75}
76
77#[derive(Debug)]
79pub struct FromStrError {
80    inner: parse::ParseError,
81}
82
83impl FromStrError {
84    fn s(&self) -> &str {
85        "mime parse error"
86    }
87}
88
89impl fmt::Display for FromStrError {
90    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91        write!(f, "{}: {}", self.s(), self.inner)
92    }
93}
94
95impl Error for FromStrError {
96    #[allow(deprecated)]
98    fn description(&self) -> &str {
99        self.s()
100    }
101}
102
103#[derive(Clone)]
104enum Source {
105    Atom(u8, &'static str),
106    Dynamic(String),
107}
108
109impl Source {
110    fn as_ref(&self) -> &str {
111        match *self {
112            Source::Atom(_, s) => s,
113            Source::Dynamic(ref s) => s,
114        }
115    }
116}
117
118#[derive(Clone)]
119enum ParamSource {
120    Utf8(usize),
121    Custom(usize, Vec<(Indexed, Indexed)>),
122    None,
123}
124
125#[derive(Clone, Copy)]
126struct Indexed(usize, usize);
127
128impl Mime {
129    #[inline]
139    pub fn type_(&self) -> Name {
140        Name {
141            source: &self.source.as_ref()[..self.slash],
142            insensitive: true,
143        }
144    }
145
146    #[inline]
156    pub fn subtype(&self) -> Name {
157        let end = self.plus.unwrap_or_else(|| {
158            return self.semicolon().unwrap_or(self.source.as_ref().len())
159        });
160        Name {
161            source: &self.source.as_ref()[self.slash + 1..end],
162            insensitive: true,
163        }
164    }
165
166    #[inline]
179    pub fn suffix(&self) -> Option<Name> {
180        let end = self.semicolon().unwrap_or(self.source.as_ref().len());
181        self.plus.map(|idx| Name {
182            source: &self.source.as_ref()[idx + 1..end],
183            insensitive: true,
184        })
185    }
186
187    pub fn get_param<'a, N>(&'a self, attr: N) -> Option<Name<'a>>
201    where N: PartialEq<Name<'a>> {
202        self.params().find(|e| attr == e.0).map(|e| e.1)
203    }
204
205    #[inline]
207    pub fn params<'a>(&'a self) -> Params<'a> {
208        let inner = match self.params {
209            ParamSource::Utf8(_) => ParamsInner::Utf8,
210            ParamSource::Custom(_, ref params) => {
211                ParamsInner::Custom {
212                    source: &self.source,
213                    params: params.iter(),
214                }
215            }
216            ParamSource::None => ParamsInner::None,
217        };
218
219        Params(inner)
220    }
221
222    pub fn essence_str(&self) -> &str {
226        let end = self.semicolon().unwrap_or(self.source.as_ref().len());
227
228        &self.source.as_ref()[..end]
229    }
230
231    #[cfg(test)]
232    fn has_params(&self) -> bool {
233        match self.params {
234            ParamSource::None => false,
235            _ => true,
236        }
237    }
238
239    #[inline]
240    fn semicolon(&self) -> Option<usize> {
241        match self.params {
242            ParamSource::Utf8(i) |
243            ParamSource::Custom(i, _) => Some(i),
244            ParamSource::None => None,
245        }
246    }
247
248    fn atom(&self) -> u8 {
249        match self.source {
250            Source::Atom(a, _) => a,
251            _ => 0,
252        }
253    }
254}
255
256fn eq_ascii(a: &str, b: &str) -> bool {
259    #[allow(deprecated, unused)]
262    use std::ascii::AsciiExt;
263
264    a.eq_ignore_ascii_case(b)
265}
266
267fn mime_eq_str(mime: &Mime, s: &str) -> bool {
268    if let ParamSource::Utf8(semicolon) = mime.params {
269        if mime.source.as_ref().len() == s.len() {
270            eq_ascii(mime.source.as_ref(), s)
271        } else {
272            params_eq(semicolon, mime.source.as_ref(), s)
273        }
274    } else if let Some(semicolon) = mime.semicolon() {
275        params_eq(semicolon, mime.source.as_ref(), s)
276    } else {
277        eq_ascii(mime.source.as_ref(), s)
278    }
279}
280
281fn params_eq(semicolon: usize, a: &str, b: &str) -> bool {
282    if b.len() < semicolon + 1 {
283        false
284    } else if !eq_ascii(&a[..semicolon], &b[..semicolon]) {
285        false
286    } else {
287        let mut a = &a[semicolon + 1..];
289        let mut b = &b[semicolon + 1..];
290        let mut sensitive;
291
292        loop {
293            a = a.trim();
294            b = b.trim();
295
296            match (a.is_empty(), b.is_empty()) {
297                (true, true) => return true,
298                (true, false) |
299                (false, true) => return false,
300                (false, false) => (),
301            }
302
303            if let Some(a_idx) = a.find('=') {
305                let a_name = {
306                    #[allow(deprecated)]
307                    { a[..a_idx].trim_left() }
308                };
309                if let Some(b_idx) = b.find('=') {
310                    let b_name = {
311                        #[allow(deprecated)]
312                        { b[..b_idx].trim_left() }
313                    };
314                    if !eq_ascii(a_name, b_name) {
315                        return false;
316                    }
317                    sensitive = a_name != CHARSET;
318                    a = &a[..a_idx];
319                    b = &b[..b_idx];
320                } else {
321                    return false;
322                }
323            } else {
324                return false;
325            }
326            let a_quoted = if a.as_bytes()[0] == b'"' {
328                a = &a[1..];
329                true
330            } else {
331                false
332            };
333            let b_quoted = if b.as_bytes()[0] == b'"' {
334                b = &b[1..];
335                true
336            } else {
337                false
338            };
339
340            let a_end = if a_quoted {
341                if let Some(quote) = a.find('"') {
342                    quote
343                } else {
344                    return false;
345                }
346            } else {
347                a.find(';').unwrap_or(a.len())
348            };
349
350            let b_end = if b_quoted {
351                if let Some(quote) = b.find('"') {
352                    quote
353                } else {
354                    return false;
355                }
356            } else {
357                b.find(';').unwrap_or(b.len())
358            };
359
360            if sensitive {
361                if !eq_ascii(&a[..a_end], &b[..b_end]) {
362                    return false;
363                }
364            } else {
365                if &a[..a_end] != &b[..b_end] {
366                    return false;
367                }
368            }
369            a = &a[a_end..];
370            b = &b[b_end..];
371        }
372    }
373}
374
375impl PartialEq for Mime {
376    #[inline]
377    fn eq(&self, other: &Mime) -> bool {
378        match (self.atom(), other.atom()) {
379            (0, _) |
385            (_, 0) => mime_eq_str(self, other.source.as_ref()),
386            (a, b) => a == b,
387        }
388    }
389}
390
391impl Eq for Mime {}
392
393impl PartialOrd for Mime {
394    fn partial_cmp(&self, other: &Mime) -> Option<Ordering> {
395        Some(self.cmp(other))
396    }
397}
398
399impl Ord for Mime {
400    fn cmp(&self, other: &Mime) -> Ordering {
401        self.source.as_ref().cmp(other.source.as_ref())
402    }
403}
404
405impl Hash for Mime {
406    fn hash<T: Hasher>(&self, hasher: &mut T) {
407        hasher.write(self.source.as_ref().as_bytes());
408    }
409}
410
411impl<'a> PartialEq<&'a str> for Mime {
412    #[inline]
413    fn eq(&self, s: & &'a str) -> bool {
414        mime_eq_str(self, *s)
415    }
416}
417
418impl<'a> PartialEq<Mime> for &'a str {
419    #[inline]
420    fn eq(&self, mime: &Mime) -> bool {
421        mime_eq_str(mime, *self)
422    }
423}
424
425impl FromStr for Mime {
426    type Err = FromStrError;
427
428    fn from_str(s: &str) -> Result<Mime, Self::Err> {
429        parse::parse(s).map_err(|e| FromStrError { inner: e })
430    }
431}
432
433impl AsRef<str> for Mime {
434    #[inline]
435    fn as_ref(&self) -> &str {
436        self.source.as_ref()
437    }
438}
439
440impl fmt::Debug for Mime {
441    #[inline]
442    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
443        fmt::Debug::fmt(self.source.as_ref(), f)
444    }
445}
446
447impl fmt::Display for Mime {
448    #[inline]
449    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
450        fmt::Display::fmt(self.source.as_ref(), f)
451    }
452}
453
454fn name_eq_str(name: &Name, s: &str) -> bool {
457    if name.insensitive {
458        eq_ascii(name.source, s)
459    } else {
460        name.source == s
461    }
462}
463
464impl<'a> Name<'a> {
465    pub fn as_str(&self) -> &'a str {
471        self.source
472    }
473}
474
475impl<'a, 'b> PartialEq<&'b str> for Name<'a> {
476    #[inline]
477    fn eq(&self, other: & &'b str) -> bool {
478        name_eq_str(self, *other)
479    }
480}
481
482impl<'a, 'b> PartialEq<Name<'a>> for &'b str {
483    #[inline]
484    fn eq(&self, other: &Name<'a>) -> bool {
485        name_eq_str(other, *self)
486    }
487}
488
489impl<'a> AsRef<str> for Name<'a> {
490    #[inline]
491    fn as_ref(&self) -> &str {
492        self.source
493    }
494}
495
496impl<'a> From<Name<'a>> for &'a str {
497    #[inline]
498    fn from(name: Name<'a>) -> &'a str {
499        name.source
500    }
501}
502
503impl<'a> fmt::Debug for Name<'a> {
504    #[inline]
505    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
506        fmt::Debug::fmt(self.source, f)
507    }
508}
509
510impl<'a> fmt::Display for Name<'a> {
511    #[inline]
512    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
513        fmt::Display::fmt(self.source, f)
514    }
515}
516
517enum ParamsInner<'a> {
520    Utf8,
521    Custom {
522        source: &'a Source,
523        params: slice::Iter<'a, (Indexed, Indexed)>,
524    },
525    None,
526}
527
528pub struct Params<'a>(ParamsInner<'a>);
530
531impl<'a> fmt::Debug for Params<'a> {
532    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
533        fmt.debug_struct("Params").finish()
534    }
535}
536
537impl<'a> Iterator for Params<'a> {
538    type Item = (Name<'a>, Name<'a>);
539
540    #[inline]
541    fn next(&mut self) -> Option<(Name<'a>, Name<'a>)> {
542        match self.0 {
543            ParamsInner::Utf8 => {
544                let value = (CHARSET, UTF_8);
545                self.0 = ParamsInner::None;
546                Some(value)
547            }
548            ParamsInner::Custom { source, ref mut params } => {
549                params.next().map(|&(name, value)| {
550                    let name = Name {
551                        source: &source.as_ref()[name.0..name.1],
552                        insensitive: true,
553                    };
554                    let value = Name {
555                        source: &source.as_ref()[value.0..value.1],
556                        insensitive: name == CHARSET,
557                    };
558                    (name, value)
559                })
560            }
561            ParamsInner::None => None
562        }
563    }
564
565    #[inline]
566    fn size_hint(&self) -> (usize, Option<usize>) {
567        match self.0 {
568            ParamsInner::Utf8 => (1, Some(1)),
569            ParamsInner::Custom { ref params, .. } => params.size_hint(),
570            ParamsInner::None => (0, Some(0)),
571        }
572    }
573}
574
575macro_rules! names {
576    ($($id:ident, $e:expr;)*) => (
577        $(
578        #[doc = $e]
579        pub const $id: Name<'static> = Name {
580            source: $e,
581            insensitive: true,
582        };
583        )*
584
585        #[test]
586        fn test_names_macro_consts() {
587            #[allow(unused, deprecated)]
588            use std::ascii::AsciiExt;
589            $(
590            assert_eq!($id.source.to_ascii_lowercase(), $id.source);
591            )*
592        }
593    )
594}
595
596names! {
597    STAR, "*";
598
599    TEXT, "text";
600    IMAGE, "image";
601    AUDIO, "audio";
602    VIDEO, "video";
603    APPLICATION, "application";
604    MULTIPART, "multipart";
605    MESSAGE, "message";
606    MODEL, "model";
607    FONT, "font";
608
609    PLAIN, "plain";
611    HTML, "html";
612    XML, "xml";
613    JAVASCRIPT, "javascript";
614    CSS, "css";
615    CSV, "csv";
616    EVENT_STREAM, "event-stream";
617    VCARD, "vcard";
618
619    JSON, "json";
621    WWW_FORM_URLENCODED, "x-www-form-urlencoded";
622    MSGPACK, "msgpack";
623    OCTET_STREAM, "octet-stream";
624    PDF, "pdf";
625
626    WOFF, "woff";
628    WOFF2, "woff2";
629
630    FORM_DATA, "form-data";
632
633    BMP, "bmp";
635    GIF, "gif";
636    JPEG, "jpeg";
637    PNG, "png";
638    SVG, "svg";
639
640    BASIC, "basic";
642    MPEG, "mpeg";
643    MP4, "mp4";
644    OGG, "ogg";
645
646    CHARSET, "charset";
648    BOUNDARY, "boundary";
649    UTF_8, "utf-8";
650}
651
652macro_rules! mimes {
653    ($($id:ident, $($piece:expr),*;)*) => (
654        #[allow(non_camel_case_types)]
655        enum __Atoms {
656            __Dynamic,
657        $(
658            $id,
659        )*
660        }
661
662        $(
663            mime_constant! {
664                $id, $($piece),*
665            }
666        )*
667
668        #[test]
669        fn test_mimes_macro_consts() {
670            let _ = [
671            $(
672            mime_constant_test! {
673                $id, $($piece),*
674            }
675            ),*
676            ].iter().enumerate().map(|(pos, &atom)| {
677                assert_eq!(pos + 1, atom as usize, "atom {} in position {}", atom, pos + 1);
678            }).collect::<Vec<()>>();
679        }
680    )
681}
682
683macro_rules! mime_constant {
684    ($id:ident, $src:expr, $slash:expr) => (
685        mime_constant!($id, $src, $slash, None);
686    );
687    ($id:ident, $src:expr, $slash:expr, $plus:expr) => (
688        mime_constant!(FULL $id, $src, $slash, $plus, ParamSource::None);
689    );
690
691    ($id:ident, $src:expr, $slash:expr, $plus:expr, $params:expr) => (
692        mime_constant!(FULL $id, $src, $slash, $plus, ParamSource::Utf8($params));
693    );
694
695
696    (FULL $id:ident, $src:expr, $slash:expr, $plus:expr, $params:expr) => (
697        #[doc = "`"]
698        #[doc = $src]
699        #[doc = "`"]
700        pub const $id: Mime = Mime {
701            source: Source::Atom(__Atoms::$id as u8, $src),
702            slash: $slash,
703            plus: $plus,
704            params: $params,
705        };
706    )
707}
708
709
710#[cfg(test)]
711macro_rules! mime_constant_test {
712    ($id:ident, $src:expr, $slash:expr) => (
713        mime_constant_test!($id, $src, $slash, None);
714    );
715    ($id:ident, $src:expr, $slash:expr, $plus:expr) => (
716        mime_constant_test!(FULL $id, $src, $slash, $plus, ParamSource::None);
717    );
718
719    ($id:ident, $src:expr, $slash:expr, $plus:expr, $params:expr) => (
720        mime_constant_test!(FULL $id, $src, $slash, $plus, ParamSource::Utf8($params));
721    );
722
723    (FULL $id:ident, $src:expr, $slash:expr, $plus:expr, $params:expr) => ({
724        let __mime = $id;
725        let __slash = __mime.as_ref().as_bytes()[$slash];
726        assert_eq!(__slash, b'/', "{:?} has {:?} at slash position {:?}", __mime, __slash as char, $slash);
727        if let Some(plus) = __mime.plus {
728            let __c = __mime.as_ref().as_bytes()[plus];
729            assert_eq!(__c, b'+', "{:?} has {:?} at plus position {:?}", __mime, __c as char, plus);
730        } else {
731            assert!(!__mime.as_ref().as_bytes().contains(&b'+'), "{:?} forgot plus", __mime);
732        }
733        if let ParamSource::Utf8(semicolon) = __mime.params {
734            assert_eq!(__mime.as_ref().as_bytes()[semicolon], b';');
735            assert_eq!(&__mime.as_ref()[semicolon..], "; charset=utf-8");
736        } else if let ParamSource::None = __mime.params {
737            assert!(!__mime.as_ref().as_bytes().contains(&b';'));
738        } else {
739            unreachable!();
740        }
741        __mime.atom()
742    })
743}
744
745
746mimes! {
747    STAR_STAR, "*/*", 1;
748
749    TEXT_STAR, "text/*", 4;
750    TEXT_PLAIN, "text/plain", 4;
751    TEXT_PLAIN_UTF_8, "text/plain; charset=utf-8", 4, None, 10;
752    TEXT_HTML, "text/html", 4;
753    TEXT_HTML_UTF_8, "text/html; charset=utf-8", 4, None, 9;
754    TEXT_CSS, "text/css", 4;
755    TEXT_CSS_UTF_8, "text/css; charset=utf-8", 4, None, 8;
756    TEXT_JAVASCRIPT, "text/javascript", 4;
757    TEXT_XML, "text/xml", 4;
758    TEXT_EVENT_STREAM, "text/event-stream", 4;
759    TEXT_CSV, "text/csv", 4;
760    TEXT_CSV_UTF_8, "text/csv; charset=utf-8", 4, None, 8;
761    TEXT_TAB_SEPARATED_VALUES, "text/tab-separated-values", 4;
762    TEXT_TAB_SEPARATED_VALUES_UTF_8, "text/tab-separated-values; charset=utf-8", 4, None, 25;
763    TEXT_VCARD, "text/vcard", 4;
764
765    IMAGE_STAR, "image/*", 5;
766    IMAGE_JPEG, "image/jpeg", 5;
767    IMAGE_GIF, "image/gif", 5;
768    IMAGE_PNG, "image/png", 5;
769    IMAGE_BMP, "image/bmp", 5;
770    IMAGE_SVG, "image/svg+xml", 5, Some(9);
771
772    FONT_WOFF, "font/woff", 4;
773    FONT_WOFF2, "font/woff2", 4;
774
775    APPLICATION_JSON, "application/json", 11;
776    APPLICATION_JAVASCRIPT, "application/javascript", 11;
777    APPLICATION_JAVASCRIPT_UTF_8, "application/javascript; charset=utf-8", 11, None, 22;
778    APPLICATION_WWW_FORM_URLENCODED, "application/x-www-form-urlencoded", 11;
779    APPLICATION_OCTET_STREAM, "application/octet-stream", 11;
780    APPLICATION_MSGPACK, "application/msgpack", 11;
781    APPLICATION_PDF, "application/pdf", 11;
782
783    MULTIPART_FORM_DATA, "multipart/form-data", 9;
784}
785
786#[deprecated(since="0.3.1", note="please use `TEXT_JAVASCRIPT` instead")]
787#[doc(hidden)]
788pub const TEXT_JAVSCRIPT: Mime = TEXT_JAVASCRIPT;
789
790
791#[cfg(test)]
792mod tests {
793    use std::str::FromStr;
794    use super::*;
795
796    #[test]
797    fn test_type_() {
798        assert_eq!(TEXT_PLAIN.type_(), TEXT);
799    }
800
801
802    #[test]
803    fn test_subtype() {
804        assert_eq!(TEXT_PLAIN.subtype(), PLAIN);
805        assert_eq!(TEXT_PLAIN_UTF_8.subtype(), PLAIN);
806        let mime = Mime::from_str("text/html+xml").unwrap();
807        assert_eq!(mime.subtype(), HTML);
808    }
809
810    #[test]
811    fn test_matching() {
812        match (TEXT_PLAIN.type_(), TEXT_PLAIN.subtype()) {
813            (TEXT, PLAIN) => (),
814            _ => unreachable!(),
815        }
816    }
817
818    #[test]
819    fn test_suffix() {
820        assert_eq!(TEXT_PLAIN.suffix(), None);
821        let mime = Mime::from_str("text/html+xml").unwrap();
822        assert_eq!(mime.suffix(), Some(XML));
823    }
824
825    #[test]
826    fn test_mime_fmt() {
827        let mime = TEXT_PLAIN;
828        assert_eq!(mime.to_string(), "text/plain");
829        let mime = TEXT_PLAIN_UTF_8;
830        assert_eq!(mime.to_string(), "text/plain; charset=utf-8");
831    }
832
833    #[test]
834    fn test_mime_from_str() {
835        assert_eq!(Mime::from_str("text/plain").unwrap(), TEXT_PLAIN);
836        assert_eq!(Mime::from_str("TEXT/PLAIN").unwrap(), TEXT_PLAIN);
837        assert_eq!(Mime::from_str("text/plain;charset=utf-8").unwrap(), TEXT_PLAIN_UTF_8);
838        assert_eq!(Mime::from_str("text/plain;charset=\"utf-8\"").unwrap(), TEXT_PLAIN_UTF_8);
839
840        assert_eq!(Mime::from_str("text/plain; charset=utf-8").unwrap(), TEXT_PLAIN_UTF_8);
842
843        Mime::from_str("text/plain;charset=\"utf-8\"; foo=bar").unwrap();
845        Mime::from_str("text/plain;charset=\"utf-8\" ; foo=bar").unwrap();
846
847        let upper = Mime::from_str("TEXT/PLAIN").unwrap();
848        assert_eq!(upper, TEXT_PLAIN);
849        assert_eq!(upper.type_(), TEXT);
850        assert_eq!(upper.subtype(), PLAIN);
851
852
853        let extended = Mime::from_str("TEXT/PLAIN; CHARSET=UTF-8; FOO=BAR").unwrap();
854        assert_eq!(extended, "text/plain; charset=utf-8; foo=BAR");
855        assert_eq!(extended.get_param("charset").unwrap(), "utf-8");
856        assert_eq!(extended.get_param("foo").unwrap(), "BAR");
857
858        Mime::from_str("multipart/form-data; boundary=--------foobar").unwrap();
859
860        assert_eq!("*/*".parse::<Mime>().unwrap(), STAR_STAR);
862        assert_eq!("image/*".parse::<Mime>().unwrap(), "image/*");
863        assert_eq!("text/*; charset=utf-8".parse::<Mime>().unwrap(), "text/*; charset=utf-8");
864
865        Mime::from_str("f o o / bar").unwrap_err();
867        Mime::from_str("text\n/plain").unwrap_err();
868        Mime::from_str("text\r/plain").unwrap_err();
869        Mime::from_str("text/\r\nplain").unwrap_err();
870        Mime::from_str("text/plain;\r\ncharset=utf-8").unwrap_err();
871        Mime::from_str("text/plain; charset=\r\nutf-8").unwrap_err();
872        Mime::from_str("text/plain; charset=\"\r\nutf-8\"").unwrap_err();
873    }
874
875    #[test]
876    fn test_mime_from_str_empty_parameter_list() {
877        static CASES: &'static [&'static str] = &[
878            "text/event-stream;",
879            "text/event-stream; ",
880            "text/event-stream;       ",
881        ];
882
883        for case in CASES {
884            let mime = Mime::from_str(case).expect(case);
885            assert_eq!(mime, TEXT_EVENT_STREAM, "case = {:?}", case);
886            assert_eq!(mime.type_(), TEXT, "case = {:?}", case);
887            assert_eq!(mime.subtype(), EVENT_STREAM, "case = {:?}", case);
888            assert!(!mime.has_params(), "case = {:?}", case);
889        }
890
891    }
892
893    #[test]
894    fn test_case_sensitive_values() {
895        let mime = Mime::from_str("multipart/form-data; charset=BASE64; boundary=ABCDEFG").unwrap();
896        assert_eq!(mime.get_param(CHARSET).unwrap(), "bAsE64");
897        assert_eq!(mime.get_param(BOUNDARY).unwrap(), "ABCDEFG");
898        assert_ne!(mime.get_param(BOUNDARY).unwrap(), "abcdefg");
899    }
900
901    #[test]
902    fn test_get_param() {
903        assert_eq!(TEXT_PLAIN.get_param("charset"), None);
904        assert_eq!(TEXT_PLAIN.get_param("baz"), None);
905
906        assert_eq!(TEXT_PLAIN_UTF_8.get_param("charset"), Some(UTF_8));
907        assert_eq!(TEXT_PLAIN_UTF_8.get_param("baz"), None);
908
909        let mime = Mime::from_str("text/plain; charset=utf-8; foo=bar").unwrap();
910        assert_eq!(mime.get_param(CHARSET).unwrap(), "utf-8");
911        assert_eq!(mime.get_param("foo").unwrap(), "bar");
912        assert_eq!(mime.get_param("baz"), None);
913
914
915        let mime = Mime::from_str("text/plain;charset=\"utf-8\"").unwrap();
916        assert_eq!(mime.get_param(CHARSET), Some(UTF_8));
917    }
918
919    #[test]
920    fn test_name_eq() {
921        assert_eq!(TEXT, TEXT);
922        assert_eq!(TEXT, "text");
923        assert_eq!("text", TEXT);
924        assert_eq!(TEXT, "TEXT");
925
926        let param = Name {
927            source: "ABC",
928            insensitive: false,
929        };
930
931        assert_eq!(param, param);
932        assert_eq!(param, "ABC");
933        assert_eq!("ABC", param);
934        assert_ne!(param, "abc");
935        assert_ne!("abc", param);
936    }
937
938    #[test]
939    fn test_essence_str() {
940        assert_eq!(TEXT_PLAIN.essence_str(), "text/plain");
941        assert_eq!(TEXT_PLAIN_UTF_8.essence_str(), "text/plain");
942        assert_eq!(IMAGE_SVG.essence_str(), "image/svg+xml");
943    }
944}