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: &str) => s,
113 Source::Dynamic(ref s: &String) => 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: usize) = mime.params {
269 if mime.source.as_ref().len() == s.len() {
270 eq_ascii(a:mime.source.as_ref(), b:s)
271 } else {
272 params_eq(semicolon, a:mime.source.as_ref(), b:s)
273 }
274 } else if let Some(semicolon: usize) = mime.semicolon() {
275 params_eq(semicolon, a:mime.source.as_ref(), b:s)
276 } else {
277 eq_ascii(a:mime.source.as_ref(), b: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, s:other.source.as_ref()),
386 (a: u8, b: u8) => 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: ParseError| 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(a:name.source, b: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(name: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(name:"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}
945