mime.rs 6.7 KB

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