mime.rs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. use std::fmt::{self, Write};
  2. use std::str::FromStr;
  3. /// <https://mimesniff.spec.whatwg.org/#mime-type-representation>
  4. #[derive(Debug, PartialEq, Eq)]
  5. pub struct Mime {
  6. pub type_: String,
  7. pub subtype: String,
  8. /// (name, value)
  9. pub parameters: Vec<(String, String)>
  10. }
  11. impl Mime {
  12. pub fn get_parameter<P>(&self, name: &P) -> Option<&str>
  13. where P: ?Sized + PartialEq<str>
  14. {
  15. self.parameters.iter()
  16. .find(|&&(ref n, _)| name == &**n)
  17. .map(|&(_, ref v)| &**v)
  18. }
  19. }
  20. #[derive(Debug)]
  21. pub struct MimeParsingError(());
  22. /// <https://mimesniff.spec.whatwg.org/#parsing-a-mime-type>
  23. impl FromStr for Mime {
  24. type Err = MimeParsingError;
  25. fn from_str(s: &str) -> Result<Self, Self::Err> {
  26. parse(s).ok_or(MimeParsingError(()))
  27. }
  28. }
  29. fn parse(s: &str) -> Option<Mime> {
  30. let trimmed = s.trim_matches(ascii_whitespace);
  31. let (type_, rest) = split2(trimmed, '/');
  32. require!(only_http_token_code_points(type_) && !type_.is_empty());
  33. let (subtype, rest) = split2(rest?, ';');
  34. let subtype = subtype.trim_right_matches(ascii_whitespace);
  35. require!(only_http_token_code_points(subtype) && !subtype.is_empty());
  36. let mut parameters = Vec::new();
  37. if let Some(rest) = rest {
  38. parse_parameters(rest, &mut parameters)
  39. }
  40. Some(Mime {
  41. type_: type_.to_ascii_lowercase(),
  42. subtype: subtype.to_ascii_lowercase(),
  43. parameters,
  44. })
  45. }
  46. fn split2(s: &str, separator: char) -> (&str, Option<&str>) {
  47. let mut iter = s.splitn(2, separator);
  48. let first = iter.next().unwrap();
  49. (first, iter.next())
  50. }
  51. fn parse_parameters(s: &str, parameters: &mut Vec<(String, String)>) {
  52. let mut semicolon_separated = s.split(';');
  53. while let Some(piece) = semicolon_separated.next() {
  54. let piece = piece.trim_left_matches(ascii_whitespace);
  55. let (name, value) = split2(piece, '=');
  56. if name.is_empty() || !only_http_token_code_points(name) || contains(&parameters, name) {
  57. continue
  58. }
  59. if let Some(value) = value {
  60. let value = if value.starts_with('"') {
  61. let max_len = value.len().saturating_sub(2); // without start or end quotes
  62. let mut unescaped_value = String::with_capacity(max_len);
  63. let mut chars = value[1..].chars();
  64. 'until_closing_quote: loop {
  65. while let Some(c) = chars.next() {
  66. match c {
  67. '"' => break 'until_closing_quote,
  68. '\\' => unescaped_value.push(chars.next().unwrap_or('\\')),
  69. _ => unescaped_value.push(c)
  70. }
  71. }
  72. if let Some(piece) = semicolon_separated.next() {
  73. // A semicolon inside a quoted value is not a separator
  74. // for the next parameter, but part of the value.
  75. unescaped_value.push(';');
  76. chars = piece.chars()
  77. } else {
  78. break
  79. }
  80. }
  81. if !valid_value(&unescaped_value) {
  82. continue
  83. }
  84. unescaped_value
  85. } else {
  86. let value = value.trim_right_matches(ascii_whitespace);
  87. if !valid_value(value) {
  88. continue
  89. }
  90. value.to_owned()
  91. };
  92. parameters.push((name.to_ascii_lowercase(), value))
  93. }
  94. }
  95. }
  96. fn contains(parameters: &[(String, String)], name: &str) -> bool {
  97. parameters.iter().any(|&(ref n, _)| n == name)
  98. }
  99. fn valid_value(s: &str) -> bool {
  100. s.chars().all(|c| {
  101. // <https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point>
  102. matches!(c, '\t' | ' '...'~' | '\u{80}'...'\u{FF}')
  103. }) && !s.is_empty()
  104. }
  105. /// <https://mimesniff.spec.whatwg.org/#serializing-a-mime-type>
  106. impl fmt::Display for Mime {
  107. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  108. f.write_str(&self.type_)?;
  109. f.write_str("/")?;
  110. f.write_str(&self.subtype)?;
  111. for &(ref name, ref value) in &self.parameters {
  112. f.write_str(";")?;
  113. f.write_str(name)?;
  114. f.write_str("=")?;
  115. if only_http_token_code_points(value) {
  116. f.write_str(value)?
  117. } else {
  118. f.write_str("\"")?;
  119. for c in value.chars() {
  120. if c == '"' || c == '\\' {
  121. f.write_str("\\")?
  122. }
  123. f.write_char(c)?
  124. }
  125. f.write_str("\"")?
  126. }
  127. }
  128. Ok(())
  129. }
  130. }
  131. fn ascii_whitespace(c: char) -> bool {
  132. matches!(c, ' ' | '\t' | '\n' | '\r' | '\x0C')
  133. }
  134. fn only_http_token_code_points(s: &str) -> bool {
  135. s.bytes().all(|byte| IS_HTTP_TOKEN[byte as usize])
  136. }
  137. macro_rules! byte_map {
  138. ($($flag:expr,)*) => ([
  139. $($flag != 0,)*
  140. ])
  141. }
  142. // Copied from https://github.com/hyperium/mime/blob/v0.3.5/src/parse.rs#L293
  143. #[rustfmt::skip]
  144. static IS_HTTP_TOKEN: [bool; 256] = byte_map![
  145. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  146. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  147. 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0,
  148. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
  149. 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  150. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
  151. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  152. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
  153. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  154. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  155. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  156. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  157. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  158. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  159. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  160. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  161. ];