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 { 77 fn s(&self) -> &str { 78 "mime parse error" 79 } 80 } 81 82 impl fmt::Display for FromStrError { 83 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)] 91 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 { 103 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] 132 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] 149 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] 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 /// ``` 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] 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 218 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)] 225 fn has_params(&self) -> bool { 226 match self.params { 227 ParamSource::None => false, 228 _ => true, 229 } 230 } 231 232 #[inline] 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 241 fn atom(&self) -> u8 { 242 match self.source { 243 Source::Atom(a, _) => a, 244 _ => 0, 245 } 246 } 247 } 248 249 // Mime ============ 250 251 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 260 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 274 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] 370 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 { 387 fn partial_cmp(&self, other: &Mime) -> Option<Ordering> { 388 Some(self.cmp(other)) 389 } 390 } 391 392 impl Ord for Mime { 393 fn cmp(&self, other: &Mime) -> Ordering { 394 self.source.as_ref().cmp(other.source.as_ref()) 395 } 396 } 397 398 impl Hash for Mime { 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] 406 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] 413 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 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] 428 fn as_ref(&self) -> &str { 429 self.source.as_ref() 430 } 431 } 432 433 impl fmt::Debug for Mime { 434 #[inline] 435 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] 442 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 443 fmt::Display::fmt(self.source.as_ref(), f) 444 } 445 } 446 447 // Name ============ 448 449 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()`. 463 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] 470 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] 477 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] 484 fn as_ref(&self) -> &str { 485 self.source 486 } 487 } 488 489 impl<'a> From<Name<'a>> for &'a str { 490 #[inline] 491 fn from(name: Name<'a>) -> &'a str { 492 name.source 493 } 494 } 495 496 impl<'a> fmt::Debug for Name<'a> { 497 #[inline] 498 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] 505 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> { 525 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] 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] 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] 790 fn test_type_() { 791 assert_eq!(TEXT_PLAIN.type_(), TEXT); 792 } 793 794 795 #[test] 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] 804 fn test_matching() { 805 match (TEXT_PLAIN.type_(), TEXT_PLAIN.subtype()) { 806 (TEXT, PLAIN) => (), 807 _ => unreachable!(), 808 } 809 } 810 811 #[test] 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] 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] 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] 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] 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] 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] 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] 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