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.16")]
27 #![deny(warnings)]
28 #![deny(missing_docs)]
29 #![deny(missing_debug_implementations)]
30
31
32 use std::cmp::Ordering;
33 use std::error::Error;
34 use std::fmt;
35 use std::hash::{Hash, Hasher};
36 use std::str::FromStr;
37 use std::slice;
38
39 mod parse;
40
41 /// A parsed mime or media type.
42 #[derive(Clone)]
43 pub struct Mime {
44 source: Source,
45 slash: usize,
46 plus: Option<usize>,
47 params: ParamSource,
48 }
49
50 /// A section of a `Mime`.
51 ///
52 /// For instance, for the Mime `image/svg+xml`, it contains 3 `Name`s,
53 /// `image`, `svg`, and `xml`.
54 ///
55 /// In most cases, `Name`s are compared ignoring case.
56 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
57 pub struct Name<'a> {
58 // TODO: optimize with an Atom-like thing
59 // There a `const` Names, and so it is possible for the statis strings
60 // to havea different memory address. Additionally, when used in match
61 // statements, the strings are compared with a memcmp, possibly even
62 // if the address and length are the same.
63 //
64 // Being an enum with an Atom variant that is a usize (and without a
65 // string pointer and boolean) would allow for faster comparisons.
66 source: &'a str,
67 insensitive: bool,
68 }
69
70 /// An error when parsing a `Mime` from a string.
71 #[derive(Debug)]
72 pub struct FromStrError {
73 inner: parse::ParseError,
74 }
75
76 impl FromStrError {
s(&self) -> &str77 fn s(&self) -> &str {
78 "mime parse error"
79 }
80 }
81
82 impl fmt::Display for FromStrError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result83 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84 write!(f, "{}: {}", self.s(), self.inner)
85 }
86 }
87
88 impl Error for FromStrError {
89 // Minimum Rust is 1.15, Error::description was still required then
90 #[allow(deprecated)]
description(&self) -> &str91 fn description(&self) -> &str {
92 self.s()
93 }
94 }
95
96 #[derive(Clone)]
97 enum Source {
98 Atom(u8, &'static str),
99 Dynamic(String),
100 }
101
102 impl Source {
as_ref(&self) -> &str103 fn as_ref(&self) -> &str {
104 match *self {
105 Source::Atom(_, s) => s,
106 Source::Dynamic(ref s) => s,
107 }
108 }
109 }
110
111 #[derive(Clone)]
112 enum ParamSource {
113 Utf8(usize),
114 Custom(usize, Vec<(Indexed, Indexed)>),
115 None,
116 }
117
118 #[derive(Clone, Copy)]
119 struct Indexed(usize, usize);
120
121 impl Mime {
122 /// Get the top level media type for this `Mime`.
123 ///
124 /// # Example
125 ///
126 /// ```
127 /// let mime = mime::TEXT_PLAIN;
128 /// assert_eq!(mime.type_(), "text");
129 /// assert_eq!(mime.type_(), mime::TEXT);
130 /// ```
131 #[inline]
type_(&self) -> Name132 pub fn type_(&self) -> Name {
133 Name {
134 source: &self.source.as_ref()[..self.slash],
135 insensitive: true,
136 }
137 }
138
139 /// Get the subtype of this `Mime`.
140 ///
141 /// # Example
142 ///
143 /// ```
144 /// let mime = mime::TEXT_PLAIN;
145 /// assert_eq!(mime.subtype(), "plain");
146 /// assert_eq!(mime.subtype(), mime::PLAIN);
147 /// ```
148 #[inline]
subtype(&self) -> Name149 pub fn subtype(&self) -> Name {
150 let end = self.plus.unwrap_or_else(|| {
151 return self.semicolon().unwrap_or(self.source.as_ref().len())
152 });
153 Name {
154 source: &self.source.as_ref()[self.slash + 1..end],
155 insensitive: true,
156 }
157 }
158
159 /// Get an optional +suffix for this `Mime`.
160 ///
161 /// # Example
162 ///
163 /// ```
164 /// let svg = "image/svg+xml".parse::<mime::Mime>().unwrap();
165 /// assert_eq!(svg.suffix(), Some(mime::XML));
166 /// assert_eq!(svg.suffix().unwrap(), "xml");
167 ///
168 ///
169 /// assert!(mime::TEXT_PLAIN.suffix().is_none());
170 /// ```
171 #[inline]
suffix(&self) -> Option<Name>172 pub fn suffix(&self) -> Option<Name> {
173 let end = self.semicolon().unwrap_or(self.source.as_ref().len());
174 self.plus.map(|idx| Name {
175 source: &self.source.as_ref()[idx + 1..end],
176 insensitive: true,
177 })
178 }
179
180 /// Look up a parameter by name.
181 ///
182 /// # Example
183 ///
184 /// ```
185 /// let mime = mime::TEXT_PLAIN_UTF_8;
186 /// assert_eq!(mime.get_param(mime::CHARSET), Some(mime::UTF_8));
187 /// assert_eq!(mime.get_param("charset").unwrap(), "utf-8");
188 /// assert!(mime.get_param("boundary").is_none());
189 ///
190 /// let mime = "multipart/form-data; boundary=ABCDEFG".parse::<mime::Mime>().unwrap();
191 /// assert_eq!(mime.get_param(mime::BOUNDARY).unwrap(), "ABCDEFG");
192 /// ```
get_param<'a, N>(&'a self, attr: N) -> Option<Name<'a>> where N: PartialEq<Name<'a>>193 pub fn get_param<'a, N>(&'a self, attr: N) -> Option<Name<'a>>
194 where N: PartialEq<Name<'a>> {
195 self.params().find(|e| attr == e.0).map(|e| e.1)
196 }
197
198 /// Returns an iterator over the parameters.
199 #[inline]
params<'a>(&'a self) -> Params<'a>200 pub fn params<'a>(&'a self) -> Params<'a> {
201 let inner = match self.params {
202 ParamSource::Utf8(_) => ParamsInner::Utf8,
203 ParamSource::Custom(_, ref params) => {
204 ParamsInner::Custom {
205 source: &self.source,
206 params: params.iter(),
207 }
208 }
209 ParamSource::None => ParamsInner::None,
210 };
211
212 Params(inner)
213 }
214
215 /// Return a `&str` of the Mime's ["essence"][essence].
216 ///
217 /// [essence]: https://mimesniff.spec.whatwg.org/#mime-type-essence
essence_str(&self) -> &str218 pub fn essence_str(&self) -> &str {
219 let end = self.semicolon().unwrap_or(self.source.as_ref().len());
220
221 &self.source.as_ref()[..end]
222 }
223
224 #[cfg(test)]
has_params(&self) -> bool225 fn has_params(&self) -> bool {
226 match self.params {
227 ParamSource::None => false,
228 _ => true,
229 }
230 }
231
232 #[inline]
semicolon(&self) -> Option<usize>233 fn semicolon(&self) -> Option<usize> {
234 match self.params {
235 ParamSource::Utf8(i) |
236 ParamSource::Custom(i, _) => Some(i),
237 ParamSource::None => None,
238 }
239 }
240
atom(&self) -> u8241 fn atom(&self) -> u8 {
242 match self.source {
243 Source::Atom(a, _) => a,
244 _ => 0,
245 }
246 }
247 }
248
249 // Mime ============
250
eq_ascii(a: &str, b: &str) -> bool251 fn eq_ascii(a: &str, b: &str) -> bool {
252 // str::eq_ignore_ascii_case didn't stabilize until Rust 1.23.
253 // So while our MSRV is 1.15, gotta import this trait.
254 #[allow(deprecated, unused)]
255 use std::ascii::AsciiExt;
256
257 a.eq_ignore_ascii_case(b)
258 }
259
mime_eq_str(mime: &Mime, s: &str) -> bool260 fn mime_eq_str(mime: &Mime, s: &str) -> bool {
261 if let ParamSource::Utf8(semicolon) = mime.params {
262 if mime.source.as_ref().len() == s.len() {
263 eq_ascii(mime.source.as_ref(), s)
264 } else {
265 params_eq(semicolon, mime.source.as_ref(), s)
266 }
267 } else if let Some(semicolon) = mime.semicolon() {
268 params_eq(semicolon, mime.source.as_ref(), s)
269 } else {
270 eq_ascii(mime.source.as_ref(), s)
271 }
272 }
273
params_eq(semicolon: usize, a: &str, b: &str) -> bool274 fn params_eq(semicolon: usize, a: &str, b: &str) -> bool {
275 if b.len() < semicolon + 1 {
276 false
277 } else if !eq_ascii(&a[..semicolon], &b[..semicolon]) {
278 false
279 } else {
280 // gotta check for quotes, LWS, and for case senstive names
281 let mut a = &a[semicolon + 1..];
282 let mut b = &b[semicolon + 1..];
283 let mut sensitive;
284
285 loop {
286 a = a.trim();
287 b = b.trim();
288
289 match (a.is_empty(), b.is_empty()) {
290 (true, true) => return true,
291 (true, false) |
292 (false, true) => return false,
293 (false, false) => (),
294 }
295
296 //name
297 if let Some(a_idx) = a.find('=') {
298 let a_name = {
299 #[allow(deprecated)]
300 { a[..a_idx].trim_left() }
301 };
302 if let Some(b_idx) = b.find('=') {
303 let b_name = {
304 #[allow(deprecated)]
305 { b[..b_idx].trim_left() }
306 };
307 if !eq_ascii(a_name, b_name) {
308 return false;
309 }
310 sensitive = a_name != CHARSET;
311 a = &a[..a_idx];
312 b = &b[..b_idx];
313 } else {
314 return false;
315 }
316 } else {
317 return false;
318 }
319 //value
320 let a_quoted = if a.as_bytes()[0] == b'"' {
321 a = &a[1..];
322 true
323 } else {
324 false
325 };
326 let b_quoted = if b.as_bytes()[0] == b'"' {
327 b = &b[1..];
328 true
329 } else {
330 false
331 };
332
333 let a_end = if a_quoted {
334 if let Some(quote) = a.find('"') {
335 quote
336 } else {
337 return false;
338 }
339 } else {
340 a.find(';').unwrap_or(a.len())
341 };
342
343 let b_end = if b_quoted {
344 if let Some(quote) = b.find('"') {
345 quote
346 } else {
347 return false;
348 }
349 } else {
350 b.find(';').unwrap_or(b.len())
351 };
352
353 if sensitive {
354 if !eq_ascii(&a[..a_end], &b[..b_end]) {
355 return false;
356 }
357 } else {
358 if &a[..a_end] != &b[..b_end] {
359 return false;
360 }
361 }
362 a = &a[a_end..];
363 b = &b[b_end..];
364 }
365 }
366 }
367
368 impl PartialEq for Mime {
369 #[inline]
eq(&self, other: &Mime) -> bool370 fn eq(&self, other: &Mime) -> bool {
371 match (self.atom(), other.atom()) {
372 // TODO:
373 // This could optimize for when there are no customs parameters.
374 // Any parsed mime has already been lowercased, so if there aren't
375 // any parameters that are case sensistive, this can skip the
376 // eq_ascii, and just use a memcmp instead.
377 (0, _) |
378 (_, 0) => mime_eq_str(self, other.source.as_ref()),
379 (a, b) => a == b,
380 }
381 }
382 }
383
384 impl Eq for Mime {}
385
386 impl PartialOrd for Mime {
partial_cmp(&self, other: &Mime) -> Option<Ordering>387 fn partial_cmp(&self, other: &Mime) -> Option<Ordering> {
388 Some(self.cmp(other))
389 }
390 }
391
392 impl Ord for Mime {
cmp(&self, other: &Mime) -> Ordering393 fn cmp(&self, other: &Mime) -> Ordering {
394 self.source.as_ref().cmp(other.source.as_ref())
395 }
396 }
397
398 impl Hash for Mime {
hash<T: Hasher>(&self, hasher: &mut T)399 fn hash<T: Hasher>(&self, hasher: &mut T) {
400 hasher.write(self.source.as_ref().as_bytes());
401 }
402 }
403
404 impl<'a> PartialEq<&'a str> for Mime {
405 #[inline]
eq(&self, s: & &'a str) -> bool406 fn eq(&self, s: & &'a str) -> bool {
407 mime_eq_str(self, *s)
408 }
409 }
410
411 impl<'a> PartialEq<Mime> for &'a str {
412 #[inline]
eq(&self, mime: &Mime) -> bool413 fn eq(&self, mime: &Mime) -> bool {
414 mime_eq_str(mime, *self)
415 }
416 }
417
418 impl FromStr for Mime {
419 type Err = FromStrError;
420
from_str(s: &str) -> Result<Mime, Self::Err>421 fn from_str(s: &str) -> Result<Mime, Self::Err> {
422 parse::parse(s).map_err(|e| FromStrError { inner: e })
423 }
424 }
425
426 impl AsRef<str> for Mime {
427 #[inline]
as_ref(&self) -> &str428 fn as_ref(&self) -> &str {
429 self.source.as_ref()
430 }
431 }
432
433 impl fmt::Debug for Mime {
434 #[inline]
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result435 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
436 fmt::Debug::fmt(self.source.as_ref(), f)
437 }
438 }
439
440 impl fmt::Display for Mime {
441 #[inline]
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result442 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
443 fmt::Display::fmt(self.source.as_ref(), f)
444 }
445 }
446
447 // Name ============
448
name_eq_str(name: &Name, s: &str) -> bool449 fn name_eq_str(name: &Name, s: &str) -> bool {
450 if name.insensitive {
451 eq_ascii(name.source, s)
452 } else {
453 name.source == s
454 }
455 }
456
457 impl<'a> Name<'a> {
458 /// Get the value of this `Name` as a string.
459 ///
460 /// Note that the borrow is not tied to `&self` but the `'a` lifetime, allowing the
461 /// string to outlive `Name`. Alternately, there is an `impl<'a> From<Name<'a>> for &'a str`
462 /// which isn't rendered by Rustdoc, that can be accessed using `str::from(name)` or `name.into()`.
as_str(&self) -> &'a str463 pub fn as_str(&self) -> &'a str {
464 self.source
465 }
466 }
467
468 impl<'a, 'b> PartialEq<&'b str> for Name<'a> {
469 #[inline]
eq(&self, other: & &'b str) -> bool470 fn eq(&self, other: & &'b str) -> bool {
471 name_eq_str(self, *other)
472 }
473 }
474
475 impl<'a, 'b> PartialEq<Name<'a>> for &'b str {
476 #[inline]
eq(&self, other: &Name<'a>) -> bool477 fn eq(&self, other: &Name<'a>) -> bool {
478 name_eq_str(other, *self)
479 }
480 }
481
482 impl<'a> AsRef<str> for Name<'a> {
483 #[inline]
as_ref(&self) -> &str484 fn as_ref(&self) -> &str {
485 self.source
486 }
487 }
488
489 impl<'a> From<Name<'a>> for &'a str {
490 #[inline]
from(name: Name<'a>) -> &'a str491 fn from(name: Name<'a>) -> &'a str {
492 name.source
493 }
494 }
495
496 impl<'a> fmt::Debug for Name<'a> {
497 #[inline]
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result498 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
499 fmt::Debug::fmt(self.source, f)
500 }
501 }
502
503 impl<'a> fmt::Display for Name<'a> {
504 #[inline]
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result505 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
506 fmt::Display::fmt(self.source, f)
507 }
508 }
509
510 // Params ===================
511
512 enum ParamsInner<'a> {
513 Utf8,
514 Custom {
515 source: &'a Source,
516 params: slice::Iter<'a, (Indexed, Indexed)>,
517 },
518 None,
519 }
520
521 /// An iterator over the parameters of a MIME.
522 pub struct Params<'a>(ParamsInner<'a>);
523
524 impl<'a> fmt::Debug for Params<'a> {
fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result525 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
526 fmt.debug_struct("Params").finish()
527 }
528 }
529
530 impl<'a> Iterator for Params<'a> {
531 type Item = (Name<'a>, Name<'a>);
532
533 #[inline]
next(&mut self) -> Option<(Name<'a>, Name<'a>)>534 fn next(&mut self) -> Option<(Name<'a>, Name<'a>)> {
535 match self.0 {
536 ParamsInner::Utf8 => {
537 let value = (CHARSET, UTF_8);
538 self.0 = ParamsInner::None;
539 Some(value)
540 }
541 ParamsInner::Custom { source, ref mut params } => {
542 params.next().map(|&(name, value)| {
543 let name = Name {
544 source: &source.as_ref()[name.0..name.1],
545 insensitive: true,
546 };
547 let value = Name {
548 source: &source.as_ref()[value.0..value.1],
549 insensitive: name == CHARSET,
550 };
551 (name, value)
552 })
553 }
554 ParamsInner::None => None
555 }
556 }
557
558 #[inline]
size_hint(&self) -> (usize, Option<usize>)559 fn size_hint(&self) -> (usize, Option<usize>) {
560 match self.0 {
561 ParamsInner::Utf8 => (1, Some(1)),
562 ParamsInner::Custom { ref params, .. } => params.size_hint(),
563 ParamsInner::None => (0, Some(0)),
564 }
565 }
566 }
567
568 macro_rules! names {
569 ($($id:ident, $e:expr;)*) => (
570 $(
571 #[doc = $e]
572 pub const $id: Name<'static> = Name {
573 source: $e,
574 insensitive: true,
575 };
576 )*
577
578 #[test]
579 fn test_names_macro_consts() {
580 #[allow(unused, deprecated)]
581 use std::ascii::AsciiExt;
582 $(
583 assert_eq!($id.source.to_ascii_lowercase(), $id.source);
584 )*
585 }
586 )
587 }
588
589 names! {
590 STAR, "*";
591
592 TEXT, "text";
593 IMAGE, "image";
594 AUDIO, "audio";
595 VIDEO, "video";
596 APPLICATION, "application";
597 MULTIPART, "multipart";
598 MESSAGE, "message";
599 MODEL, "model";
600 FONT, "font";
601
602 // common text/ *
603 PLAIN, "plain";
604 HTML, "html";
605 XML, "xml";
606 JAVASCRIPT, "javascript";
607 CSS, "css";
608 CSV, "csv";
609 EVENT_STREAM, "event-stream";
610 VCARD, "vcard";
611
612 // common application/*
613 JSON, "json";
614 WWW_FORM_URLENCODED, "x-www-form-urlencoded";
615 MSGPACK, "msgpack";
616 OCTET_STREAM, "octet-stream";
617 PDF, "pdf";
618
619 // common font/*
620 WOFF, "woff";
621 WOFF2, "woff2";
622
623 // multipart/*
624 FORM_DATA, "form-data";
625
626 // common image/*
627 BMP, "bmp";
628 GIF, "gif";
629 JPEG, "jpeg";
630 PNG, "png";
631 SVG, "svg";
632
633 // audio/*
634 BASIC, "basic";
635 MPEG, "mpeg";
636 MP4, "mp4";
637 OGG, "ogg";
638
639 // parameters
640 CHARSET, "charset";
641 BOUNDARY, "boundary";
642 UTF_8, "utf-8";
643 }
644
645 macro_rules! mimes {
646 ($($id:ident, $($piece:expr),*;)*) => (
647 #[allow(non_camel_case_types)]
648 enum __Atoms {
649 __Dynamic,
650 $(
651 $id,
652 )*
653 }
654
655 $(
656 mime_constant! {
657 $id, $($piece),*
658 }
659 )*
660
661 #[test]
662 fn test_mimes_macro_consts() {
663 let _ = [
664 $(
665 mime_constant_test! {
666 $id, $($piece),*
667 }
668 ),*
669 ].iter().enumerate().map(|(pos, &atom)| {
670 assert_eq!(pos + 1, atom as usize, "atom {} in position {}", atom, pos + 1);
671 }).collect::<Vec<()>>();
672 }
673 )
674 }
675
676 macro_rules! mime_constant {
677 ($id:ident, $src:expr, $slash:expr) => (
678 mime_constant!($id, $src, $slash, None);
679 );
680 ($id:ident, $src:expr, $slash:expr, $plus:expr) => (
681 mime_constant!(FULL $id, $src, $slash, $plus, ParamSource::None);
682 );
683
684 ($id:ident, $src:expr, $slash:expr, $plus:expr, $params:expr) => (
685 mime_constant!(FULL $id, $src, $slash, $plus, ParamSource::Utf8($params));
686 );
687
688
689 (FULL $id:ident, $src:expr, $slash:expr, $plus:expr, $params:expr) => (
690 #[doc = "`"]
691 #[doc = $src]
692 #[doc = "`"]
693 pub const $id: Mime = Mime {
694 source: Source::Atom(__Atoms::$id as u8, $src),
695 slash: $slash,
696 plus: $plus,
697 params: $params,
698 };
699 )
700 }
701
702
703 #[cfg(test)]
704 macro_rules! mime_constant_test {
705 ($id:ident, $src:expr, $slash:expr) => (
706 mime_constant_test!($id, $src, $slash, None);
707 );
708 ($id:ident, $src:expr, $slash:expr, $plus:expr) => (
709 mime_constant_test!(FULL $id, $src, $slash, $plus, ParamSource::None);
710 );
711
712 ($id:ident, $src:expr, $slash:expr, $plus:expr, $params:expr) => (
713 mime_constant_test!(FULL $id, $src, $slash, $plus, ParamSource::Utf8($params));
714 );
715
716 (FULL $id:ident, $src:expr, $slash:expr, $plus:expr, $params:expr) => ({
717 let __mime = $id;
718 let __slash = __mime.as_ref().as_bytes()[$slash];
719 assert_eq!(__slash, b'/', "{:?} has {:?} at slash position {:?}", __mime, __slash as char, $slash);
720 if let Some(plus) = __mime.plus {
721 let __c = __mime.as_ref().as_bytes()[plus];
722 assert_eq!(__c, b'+', "{:?} has {:?} at plus position {:?}", __mime, __c as char, plus);
723 } else {
724 assert!(!__mime.as_ref().as_bytes().contains(&b'+'), "{:?} forgot plus", __mime);
725 }
726 if let ParamSource::Utf8(semicolon) = __mime.params {
727 assert_eq!(__mime.as_ref().as_bytes()[semicolon], b';');
728 assert_eq!(&__mime.as_ref()[semicolon..], "; charset=utf-8");
729 } else if let ParamSource::None = __mime.params {
730 assert!(!__mime.as_ref().as_bytes().contains(&b';'));
731 } else {
732 unreachable!();
733 }
734 __mime.atom()
735 })
736 }
737
738
739 mimes! {
740 STAR_STAR, "*/*", 1;
741
742 TEXT_STAR, "text/*", 4;
743 TEXT_PLAIN, "text/plain", 4;
744 TEXT_PLAIN_UTF_8, "text/plain; charset=utf-8", 4, None, 10;
745 TEXT_HTML, "text/html", 4;
746 TEXT_HTML_UTF_8, "text/html; charset=utf-8", 4, None, 9;
747 TEXT_CSS, "text/css", 4;
748 TEXT_CSS_UTF_8, "text/css; charset=utf-8", 4, None, 8;
749 TEXT_JAVASCRIPT, "text/javascript", 4;
750 TEXT_XML, "text/xml", 4;
751 TEXT_EVENT_STREAM, "text/event-stream", 4;
752 TEXT_CSV, "text/csv", 4;
753 TEXT_CSV_UTF_8, "text/csv; charset=utf-8", 4, None, 8;
754 TEXT_TAB_SEPARATED_VALUES, "text/tab-separated-values", 4;
755 TEXT_TAB_SEPARATED_VALUES_UTF_8, "text/tab-separated-values; charset=utf-8", 4, None, 25;
756 TEXT_VCARD, "text/vcard", 4;
757
758 IMAGE_STAR, "image/*", 5;
759 IMAGE_JPEG, "image/jpeg", 5;
760 IMAGE_GIF, "image/gif", 5;
761 IMAGE_PNG, "image/png", 5;
762 IMAGE_BMP, "image/bmp", 5;
763 IMAGE_SVG, "image/svg+xml", 5, Some(9);
764
765 FONT_WOFF, "font/woff", 4;
766 FONT_WOFF2, "font/woff2", 4;
767
768 APPLICATION_JSON, "application/json", 11;
769 APPLICATION_JAVASCRIPT, "application/javascript", 11;
770 APPLICATION_JAVASCRIPT_UTF_8, "application/javascript; charset=utf-8", 11, None, 22;
771 APPLICATION_WWW_FORM_URLENCODED, "application/x-www-form-urlencoded", 11;
772 APPLICATION_OCTET_STREAM, "application/octet-stream", 11;
773 APPLICATION_MSGPACK, "application/msgpack", 11;
774 APPLICATION_PDF, "application/pdf", 11;
775
776 MULTIPART_FORM_DATA, "multipart/form-data", 9;
777 }
778
779 #[deprecated(since="0.3.1", note="please use `TEXT_JAVASCRIPT` instead")]
780 #[doc(hidden)]
781 pub const TEXT_JAVSCRIPT: Mime = TEXT_JAVASCRIPT;
782
783
784 #[cfg(test)]
785 mod tests {
786 use std::str::FromStr;
787 use super::*;
788
789 #[test]
test_type_()790 fn test_type_() {
791 assert_eq!(TEXT_PLAIN.type_(), TEXT);
792 }
793
794
795 #[test]
test_subtype()796 fn test_subtype() {
797 assert_eq!(TEXT_PLAIN.subtype(), PLAIN);
798 assert_eq!(TEXT_PLAIN_UTF_8.subtype(), PLAIN);
799 let mime = Mime::from_str("text/html+xml").unwrap();
800 assert_eq!(mime.subtype(), HTML);
801 }
802
803 #[test]
test_matching()804 fn test_matching() {
805 match (TEXT_PLAIN.type_(), TEXT_PLAIN.subtype()) {
806 (TEXT, PLAIN) => (),
807 _ => unreachable!(),
808 }
809 }
810
811 #[test]
test_suffix()812 fn test_suffix() {
813 assert_eq!(TEXT_PLAIN.suffix(), None);
814 let mime = Mime::from_str("text/html+xml").unwrap();
815 assert_eq!(mime.suffix(), Some(XML));
816 }
817
818 #[test]
test_mime_fmt()819 fn test_mime_fmt() {
820 let mime = TEXT_PLAIN;
821 assert_eq!(mime.to_string(), "text/plain");
822 let mime = TEXT_PLAIN_UTF_8;
823 assert_eq!(mime.to_string(), "text/plain; charset=utf-8");
824 }
825
826 #[test]
test_mime_from_str()827 fn test_mime_from_str() {
828 assert_eq!(Mime::from_str("text/plain").unwrap(), TEXT_PLAIN);
829 assert_eq!(Mime::from_str("TEXT/PLAIN").unwrap(), TEXT_PLAIN);
830 assert_eq!(Mime::from_str("text/plain;charset=utf-8").unwrap(), TEXT_PLAIN_UTF_8);
831 assert_eq!(Mime::from_str("text/plain;charset=\"utf-8\"").unwrap(), TEXT_PLAIN_UTF_8);
832
833 // spaces
834 assert_eq!(Mime::from_str("text/plain; charset=utf-8").unwrap(), TEXT_PLAIN_UTF_8);
835
836 // quotes + semi colon
837 Mime::from_str("text/plain;charset=\"utf-8\"; foo=bar").unwrap();
838 Mime::from_str("text/plain;charset=\"utf-8\" ; foo=bar").unwrap();
839
840 let upper = Mime::from_str("TEXT/PLAIN").unwrap();
841 assert_eq!(upper, TEXT_PLAIN);
842 assert_eq!(upper.type_(), TEXT);
843 assert_eq!(upper.subtype(), PLAIN);
844
845
846 let extended = Mime::from_str("TEXT/PLAIN; CHARSET=UTF-8; FOO=BAR").unwrap();
847 assert_eq!(extended, "text/plain; charset=utf-8; foo=BAR");
848 assert_eq!(extended.get_param("charset").unwrap(), "utf-8");
849 assert_eq!(extended.get_param("foo").unwrap(), "BAR");
850
851 Mime::from_str("multipart/form-data; boundary=--------foobar").unwrap();
852
853 // stars
854 assert_eq!("*/*".parse::<Mime>().unwrap(), STAR_STAR);
855 assert_eq!("image/*".parse::<Mime>().unwrap(), "image/*");
856 assert_eq!("text/*; charset=utf-8".parse::<Mime>().unwrap(), "text/*; charset=utf-8");
857
858 // parse errors
859 Mime::from_str("f o o / bar").unwrap_err();
860 Mime::from_str("text\n/plain").unwrap_err();
861 Mime::from_str("text\r/plain").unwrap_err();
862 Mime::from_str("text/\r\nplain").unwrap_err();
863 Mime::from_str("text/plain;\r\ncharset=utf-8").unwrap_err();
864 Mime::from_str("text/plain; charset=\r\nutf-8").unwrap_err();
865 Mime::from_str("text/plain; charset=\"\r\nutf-8\"").unwrap_err();
866 }
867
868 #[test]
test_mime_from_str_empty_parameter_list()869 fn test_mime_from_str_empty_parameter_list() {
870 static CASES: &'static [&'static str] = &[
871 "text/event-stream;",
872 "text/event-stream; ",
873 "text/event-stream; ",
874 ];
875
876 for case in CASES {
877 let mime = Mime::from_str(case).expect(case);
878 assert_eq!(mime, TEXT_EVENT_STREAM, "case = {:?}", case);
879 assert_eq!(mime.type_(), TEXT, "case = {:?}", case);
880 assert_eq!(mime.subtype(), EVENT_STREAM, "case = {:?}", case);
881 assert!(!mime.has_params(), "case = {:?}", case);
882 }
883
884 }
885
886 #[test]
test_case_sensitive_values()887 fn test_case_sensitive_values() {
888 let mime = Mime::from_str("multipart/form-data; charset=BASE64; boundary=ABCDEFG").unwrap();
889 assert_eq!(mime.get_param(CHARSET).unwrap(), "bAsE64");
890 assert_eq!(mime.get_param(BOUNDARY).unwrap(), "ABCDEFG");
891 assert_ne!(mime.get_param(BOUNDARY).unwrap(), "abcdefg");
892 }
893
894 #[test]
test_get_param()895 fn test_get_param() {
896 assert_eq!(TEXT_PLAIN.get_param("charset"), None);
897 assert_eq!(TEXT_PLAIN.get_param("baz"), None);
898
899 assert_eq!(TEXT_PLAIN_UTF_8.get_param("charset"), Some(UTF_8));
900 assert_eq!(TEXT_PLAIN_UTF_8.get_param("baz"), None);
901
902 let mime = Mime::from_str("text/plain; charset=utf-8; foo=bar").unwrap();
903 assert_eq!(mime.get_param(CHARSET).unwrap(), "utf-8");
904 assert_eq!(mime.get_param("foo").unwrap(), "bar");
905 assert_eq!(mime.get_param("baz"), None);
906
907
908 let mime = Mime::from_str("text/plain;charset=\"utf-8\"").unwrap();
909 assert_eq!(mime.get_param(CHARSET), Some(UTF_8));
910 }
911
912 #[test]
test_name_eq()913 fn test_name_eq() {
914 assert_eq!(TEXT, TEXT);
915 assert_eq!(TEXT, "text");
916 assert_eq!("text", TEXT);
917 assert_eq!(TEXT, "TEXT");
918
919 let param = Name {
920 source: "ABC",
921 insensitive: false,
922 };
923
924 assert_eq!(param, param);
925 assert_eq!(param, "ABC");
926 assert_eq!("ABC", param);
927 assert_ne!(param, "abc");
928 assert_ne!("abc", param);
929 }
930
931 #[test]
test_essence_str()932 fn test_essence_str() {
933 assert_eq!(TEXT_PLAIN.essence_str(), "text/plain");
934 assert_eq!(TEXT_PLAIN_UTF_8.essence_str(), "text/plain");
935 assert_eq!(IMAGE_SVG.essence_str(), "image/svg+xml");
936 }
937 }
938