1use omnom::prelude::*;
2use std::collections::HashMap;
3use std::io::{BufRead, Cursor, Read};
4
5fn main() {
6 assert_eq!(
7 parse_mime("text/html").unwrap(),
8 Mime {
9 base_type: "text".to_string(),
10 sub_type: "html".to_string(),
11 parameters: None,
12 }
13 );
14
15 let mut parameters = HashMap::new();
16 parameters.insert("charset".to_string(), "utf-8".to_string());
17 assert_eq!(
18 parse_mime("text/html; charset=utf-8;").unwrap(),
19 Mime {
20 base_type: "text".to_string(),
21 sub_type: "html".to_string(),
22 parameters: Some(parameters),
23 }
24 );
25}
26
27#[derive(Eq, PartialEq, Debug)]
28pub struct Mime {
29 base_type: String,
30 sub_type: String,
31 parameters: Option<HashMap<String, String>>,
32}
33
34fn parse_mime(s: &str) -> Option<Mime> {
35 let mut s = Cursor::new(s);
42 let mut base_type = vec![];
43 match s.read_until(b'/', &mut base_type).unwrap() {
44 0 => return None,
45 _ => base_type.pop(),
46 };
47 validate_code_points(&base_type)?;
48
49 let mut sub_type = vec![];
56 s.read_until(b';', &mut sub_type).unwrap();
57 if let Some(b';') = sub_type.last() {
58 sub_type.pop();
59 }
60 validate_code_points(&sub_type)?;
61
62 let mut mime = Mime {
64 base_type: String::from_utf8(base_type).unwrap(),
65 sub_type: String::from_utf8(sub_type).unwrap(),
66 parameters: None,
67 };
68
69 loop {
76 if s.fill_buf().unwrap().len() == 0 {
78 break;
79 }
80
81 s.skip_while(is_http_whitespace_char).ok()?;
88
89 let mut param_name = vec![];
96 s.read_while(&mut param_name, |b| b != b';' && b != b'=')
97 .ok()?;
98 validate_code_points(¶m_name)?;
99 let mut param_name = String::from_utf8(param_name).ok()?;
100 param_name.make_ascii_lowercase();
101
102 let mut token = vec![0; 1];
109 s.read_exact(&mut token).unwrap();
110 if token[0] == b';' {
111 continue;
112 }
113
114 let mut param_value = vec![];
121 s.read_until(b';', &mut param_value).ok()?;
122 if let Some(b';') = param_value.last() {
123 param_value.pop();
124 }
125 validate_code_points(¶m_value)?;
126 let mut param_value = String::from_utf8(param_value).ok()?;
127 param_value.make_ascii_lowercase();
128
129 if let None = mime.parameters {
131 mime.parameters = Some(HashMap::new());
132 }
133 mime.parameters.as_mut()?.insert(param_name, param_value);
134 }
135
136 Some(mime)
137}
138
139fn validate_code_points(buf: &[u8]) -> Option<()> {
140 let all = buf.iter().all(|b| match b {
141 b'-' | b'!' | b'#' | b'$' | b'%' => true,
142 b'&' | b'\'' | b'*' | b'+' | b'.' => true,
143 b'^' | b'_' | b'`' | b'|' | b'~' => true,
144 b'A'..=b'Z' => true,
145 b'a'..=b'z' => true,
146 b'0'..=b'9' => true,
147 _ => false,
148 });
149
150 if all {
151 Some(())
152 } else {
153 None
154 }
155}
156
157fn is_http_whitespace_char(b: u8) -> bool {
158 match b {
159 b' ' | b'\t' | b'\n' | b'\r' => true,
160 _ => false,
161 }
162}