mime.rs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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(ascii_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(ascii_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. #[allow(clippy::manual_strip)] // introduced in 1.45, MSRV is 1.36
  54. fn parse_parameters(s: &str, parameters: &mut Vec<(String, String)>) {
  55. let mut semicolon_separated = s.split(';');
  56. while let Some(piece) = semicolon_separated.next() {
  57. let piece = piece.trim_start_matches(ascii_whitespace);
  58. let (name, value) = split2(piece, '=');
  59. if name.is_empty() || !only_http_token_code_points(name) || contains(&parameters, name) {
  60. continue;
  61. }
  62. if let Some(value) = value {
  63. let value = if value.starts_with('"') {
  64. let max_len = value.len().saturating_sub(2); // without start or end quotes
  65. let mut unescaped_value = String::with_capacity(max_len);
  66. let mut chars = value[1..].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('\\')),
  72. _ => unescaped_value.push(c),
  73. }
  74. }
  75. if let Some(piece) = semicolon_separated.next() {
  76. // A semicolon inside a quoted value is not a separator
  77. // for the next parameter, but part of the value.
  78. unescaped_value.push(';');
  79. chars = piece.chars()
  80. } else {
  81. break;
  82. }
  83. }
  84. if !valid_value(&unescaped_value) {
  85. continue;
  86. }
  87. unescaped_value
  88. } else {
  89. let value = value.trim_end_matches(ascii_whitespace);
  90. if !valid_value(value) {
  91. continue;
  92. }
  93. value.to_owned()
  94. };
  95. parameters.push((name.to_ascii_lowercase(), value))
  96. }
  97. }
  98. }
  99. fn contains(parameters: &[(String, String)], name: &str) -> bool {
  100. parameters.iter().any(|&(ref n, _)| n == name)
  101. }
  102. fn valid_value(s: &str) -> bool {
  103. s.chars().all(|c| {
  104. // <https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point>
  105. matches!(c, '\t' | ' '..='~' | '\u{80}'..='\u{FF}')
  106. }) && !s.is_empty()
  107. }
  108. /// <https://mimesniff.spec.whatwg.org/#serializing-a-mime-type>
  109. impl fmt::Display for Mime {
  110. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  111. f.write_str(&self.type_)?;
  112. f.write_str("/")?;
  113. f.write_str(&self.subtype)?;
  114. for &(ref name, ref value) in &self.parameters {
  115. f.write_str(";")?;
  116. f.write_str(name)?;
  117. f.write_str("=")?;
  118. if only_http_token_code_points(value) {
  119. f.write_str(value)?
  120. } else {
  121. f.write_str("\"")?;
  122. for c in value.chars() {
  123. if c == '"' || c == '\\' {
  124. f.write_str("\\")?
  125. }
  126. f.write_char(c)?
  127. }
  128. f.write_str("\"")?
  129. }
  130. }
  131. Ok(())
  132. }
  133. }
  134. fn ascii_whitespace(c: char) -> bool {
  135. matches!(c, ' ' | '\t' | '\n' | '\r' | '\x0C')
  136. }
  137. fn only_http_token_code_points(s: &str) -> bool {
  138. s.bytes().all(|byte| IS_HTTP_TOKEN[byte as usize])
  139. }
  140. macro_rules! byte_map {
  141. ($($flag:expr,)*) => ([
  142. $($flag != 0,)*
  143. ])
  144. }
  145. // Copied from https://github.com/hyperium/mime/blob/v0.3.5/src/parse.rs#L293
  146. #[rustfmt::skip]
  147. static IS_HTTP_TOKEN: [bool; 256] = byte_map![
  148. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  149. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  150. 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0,
  151. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
  152. 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  153. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
  154. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  155. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 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. 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  164. ];