mime/
lib.rs

1//! # Mime
2//!
3//! Mime is now Media Type, technically, but `Mime` is more immediately
4//! understandable, so the main type here is `Mime`.
5//!
6//! ## What is Mime?
7//!
8//! Example mime string: `text/plain`
9//!
10//! ```
11//! let plain_text: mime::Mime = "text/plain".parse().unwrap();
12//! assert_eq!(plain_text, mime::TEXT_PLAIN);
13//! ```
14//!
15//! ## Inspecting Mimes
16//!
17//! ```
18//! let mime = mime::TEXT_PLAIN;
19//! match (mime.type_(), mime.subtype()) {
20//!     (mime::TEXT, mime::PLAIN) => println!("plain text!"),
21//!     (mime::TEXT, _) => println!("structured text"),
22//!     _ => println!("not text"),
23//! }
24//! ```
25
26#![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/// A parsed mime or media type.
42#[derive(Clone)]
43pub struct Mime {
44    source: Source,
45    slash: usize,
46    plus: Option<usize>,
47    params: ParamSource,
48}
49
50/// An iterator of parsed mime
51#[derive(Clone, Debug)]
52pub struct MimeIter<'a> {
53    pos: usize,
54    source: &'a str,
55}
56
57/// A section of a `Mime`.
58///
59/// For instance, for the Mime `image/svg+xml`, it contains 3 `Name`s,
60/// `image`, `svg`, and `xml`.
61///
62/// In most cases, `Name`s are compared ignoring case.
63#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub struct Name<'a> {
65    // TODO: optimize with an Atom-like thing
66    // There a `const` Names, and so it is possible for the statis strings
67    // to havea different memory address. Additionally, when used in match
68    // statements, the strings are compared with a memcmp, possibly even
69    // if the address and length are the same.
70    //
71    // Being an enum with an Atom variant that is a usize (and without a
72    // string pointer and boolean) would allow for faster comparisons.
73    source: &'a str,
74    insensitive: bool,
75}
76
77/// An error when parsing a `Mime` from a string.
78#[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    // Minimum Rust is 1.15, Error::description was still required then
97    #[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    /// Get the top level media type for this `Mime`.
130    ///
131    /// # Example
132    ///
133    /// ```
134    /// let mime = mime::TEXT_PLAIN;
135    /// assert_eq!(mime.type_(), "text");
136    /// assert_eq!(mime.type_(), mime::TEXT);
137    /// ```
138    #[inline]
139    pub fn type_(&self) -> Name {
140        Name {
141            source: &self.source.as_ref()[..self.slash],
142            insensitive: true,
143        }
144    }
145
146    /// Get the subtype of this `Mime`.
147    ///
148    /// # Example
149    ///
150    /// ```
151    /// let mime = mime::TEXT_PLAIN;
152    /// assert_eq!(mime.subtype(), "plain");
153    /// assert_eq!(mime.subtype(), mime::PLAIN);
154    /// ```
155    #[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    /// Get an optional +suffix for this `Mime`.
167    ///
168    /// # Example
169    ///
170    /// ```
171    /// let svg = "image/svg+xml".parse::<mime::Mime>().unwrap();
172    /// assert_eq!(svg.suffix(), Some(mime::XML));
173    /// assert_eq!(svg.suffix().unwrap(), "xml");
174    ///
175    ///
176    /// assert!(mime::TEXT_PLAIN.suffix().is_none());
177    /// ```
178    #[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    /// Look up a parameter by name.
188    ///
189    /// # Example
190    ///
191    /// ```
192    /// let mime = mime::TEXT_PLAIN_UTF_8;
193    /// assert_eq!(mime.get_param(mime::CHARSET), Some(mime::UTF_8));
194    /// assert_eq!(mime.get_param("charset").unwrap(), "utf-8");
195    /// assert!(mime.get_param("boundary").is_none());
196    ///
197    /// let mime = "multipart/form-data; boundary=ABCDEFG".parse::<mime::Mime>().unwrap();
198    /// assert_eq!(mime.get_param(mime::BOUNDARY).unwrap(), "ABCDEFG");
199    /// ```
200    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    /// Returns an iterator over the parameters.
206    #[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    /// Return a `&str` of the Mime's ["essence"][essence].
223    ///
224    /// [essence]: https://mimesniff.spec.whatwg.org/#mime-type-essence
225    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
256// Mime ============
257
258fn eq_ascii(a: &str, b: &str) -> bool {
259    // str::eq_ignore_ascii_case didn't stabilize until Rust 1.23.
260    // So while our MSRV is 1.15, gotta import this trait.
261    #[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        // gotta check for quotes, LWS, and for case senstive names
288        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            //name
304            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            //value
327            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            // TODO:
380            // This could optimize for when there are no customs parameters.
381            // Any parsed mime has already been lowercased, so if there aren't
382            // any parameters that are case sensistive, this can skip the
383            // eq_ascii, and just use a memcmp instead.
384            (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
454// Name ============
455
456fn 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    /// Get the value of this `Name` as a string.
466    ///
467    /// Note that the borrow is not tied to `&self` but the `'a` lifetime, allowing the
468    /// string to outlive `Name`. Alternately, there is an `impl<'a> From<Name<'a>> for &'a str`
469    /// which isn't rendered by Rustdoc, that can be accessed using `str::from(name)` or `name.into()`.
470    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
517// Params ===================
518
519enum ParamsInner<'a> {
520    Utf8,
521    Custom {
522        source: &'a Source,
523        params: slice::Iter<'a, (Indexed, Indexed)>,
524    },
525    None,
526}
527
528/// An iterator over the parameters of a MIME.
529pub 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    // common text/ *
610    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    // common application/*
620    JSON, "json";
621    WWW_FORM_URLENCODED, "x-www-form-urlencoded";
622    MSGPACK, "msgpack";
623    OCTET_STREAM, "octet-stream";
624    PDF, "pdf";
625
626    // common font/*
627    WOFF, "woff";
628    WOFF2, "woff2";
629
630    // multipart/*
631    FORM_DATA, "form-data";
632
633    // common image/*
634    BMP, "bmp";
635    GIF, "gif";
636    JPEG, "jpeg";
637    PNG, "png";
638    SVG, "svg";
639
640    // audio/*
641    BASIC, "basic";
642    MPEG, "mpeg";
643    MP4, "mp4";
644    OGG, "ogg";
645
646    // parameters
647    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        // spaces
841        assert_eq!(Mime::from_str("text/plain; charset=utf-8").unwrap(), TEXT_PLAIN_UTF_8);
842
843        // quotes + semi colon
844        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        // stars
861        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        // parse errors
866        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}