mime.rs 7.0 KB

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