Skip to content

Commit 2927208

Browse files
authored
Merge pull request #105 from http-rs/utf-8-mime
const utf8 mime
2 parents 6721feb + 2dbd5b6 commit 2927208

File tree

4 files changed

+121
-49
lines changed

4 files changed

+121
-49
lines changed

src/body.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ impl From<String> for Body {
190190
Self {
191191
length: Some(s.len()),
192192
reader: Box::new(io::Cursor::new(s.into_bytes())),
193-
mime: string_mime(),
193+
mime: mime::PLAIN,
194194
}
195195
}
196196
}
@@ -200,19 +200,11 @@ impl<'a> From<&'a str> for Body {
200200
Self {
201201
length: Some(s.len()),
202202
reader: Box::new(io::Cursor::new(s.to_owned().into_bytes())),
203-
mime: string_mime(),
203+
mime: mime::PLAIN,
204204
}
205205
}
206206
}
207207

208-
fn string_mime() -> mime::Mime {
209-
let mut mime = mime::PLAIN;
210-
let mut parameters = std::collections::HashMap::new();
211-
parameters.insert("charset".to_owned(), "utf-8".to_owned());
212-
mime.parameters = Some(parameters);
213-
mime
214-
}
215-
216208
impl From<Vec<u8>> for Body {
217209
fn from(b: Vec<u8>) -> Self {
218210
Self {

src/mime/constants.rs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use super::ParamKind;
12
use crate::Mime;
23

34
/// Content-Type that matches anything.
@@ -11,7 +12,7 @@ pub const ANY: Mime = Mime {
1112
essence: String::new(),
1213
basetype: String::new(),
1314
subtype: String::new(),
14-
parameters: None,
15+
params: None,
1516
static_essence: Some("*/*"),
1617
static_basetype: Some("*"),
1718
static_subtype: Some("*"),
@@ -22,16 +23,16 @@ pub const ANY: Mime = Mime {
2223
/// # Mime Type
2324
///
2425
/// ```txt
25-
/// application/javascript
26+
/// application/javascript; charset=utf-8
2627
/// ```
2728
pub const JAVASCRIPT: Mime = Mime {
2829
static_essence: Some("application/javascript"),
2930
essence: String::new(),
3031
basetype: String::new(),
3132
subtype: String::new(),
33+
params: Some(ParamKind::Utf8),
3234
static_basetype: Some("application"),
3335
static_subtype: Some("javascript"),
34-
parameters: None,
3536
};
3637

3738
/// Content-Type for JSON.
@@ -46,43 +47,43 @@ pub const JSON: Mime = Mime {
4647
essence: String::new(),
4748
basetype: String::new(),
4849
subtype: String::new(),
50+
params: None,
4951
static_basetype: Some("application"),
5052
static_subtype: Some("json"),
51-
parameters: None,
5253
};
5354

5455
/// Content-Type for CSS.
5556
///
5657
/// # Mime Type
5758
///
5859
/// ```txt
59-
/// text/css
60+
/// text/css; charset=utf-8
6061
/// ```
6162
pub const CSS: Mime = Mime {
6263
static_essence: Some("text/css"),
6364
essence: String::new(),
6465
basetype: String::new(),
6566
subtype: String::new(),
67+
params: Some(ParamKind::Utf8),
6668
static_basetype: Some("text"),
6769
static_subtype: Some("css"),
68-
parameters: None,
6970
};
7071

7172
/// Content-Type for HTML.
7273
///
7374
/// # Mime Type
7475
///
7576
/// ```txt
76-
/// text/html
77+
/// text/html; charset=utf-8
7778
/// ```
7879
pub const HTML: Mime = Mime {
7980
static_essence: Some("text/html"),
8081
essence: String::new(),
8182
basetype: String::new(),
8283
subtype: String::new(),
84+
params: Some(ParamKind::Utf8),
8385
static_basetype: Some("text"),
8486
static_subtype: Some("html"),
85-
parameters: None,
8687
};
8788

8889
/// Content-Type for Server Sent Events
@@ -99,24 +100,24 @@ pub const SSE: Mime = Mime {
99100
subtype: String::new(),
100101
static_basetype: Some("text"),
101102
static_subtype: Some("event-stream"),
102-
parameters: None,
103+
params: None,
103104
};
104105

105106
/// Content-Type for plain text.
106107
///
107108
/// # Mime Type
108109
///
109110
/// ```txt
110-
/// text/plain
111+
/// text/plain; charset=utf-8
111112
/// ```
112113
pub const PLAIN: Mime = Mime {
113114
static_essence: Some("text/plain"),
114115
essence: String::new(),
115116
basetype: String::new(),
116117
subtype: String::new(),
118+
params: Some(ParamKind::Utf8),
117119
static_basetype: Some("text"),
118120
static_subtype: Some("plain"),
119-
parameters: None,
120121
};
121122

122123
/// Content-Type for byte streams.
@@ -133,7 +134,7 @@ pub const BYTE_STREAM: Mime = Mime {
133134
subtype: String::new(),
134135
static_basetype: Some("application"),
135136
static_subtype: Some("octet-stream"),
136-
parameters: None,
137+
params: None,
137138
};
138139

139140
/// Content-Type for form.
@@ -150,7 +151,7 @@ pub const FORM: Mime = Mime {
150151
subtype: String::new(),
151152
static_basetype: Some("application"),
152153
static_subtype: Some("x-www-form-urlencoded"),
153-
parameters: None,
154+
params: None,
154155
};
155156

156157
/// Content-Type for a multipart form.
@@ -167,5 +168,5 @@ pub const MULTIPART_FORM: Mime = Mime {
167168
subtype: String::new(),
168169
static_basetype: Some("multipart"),
169170
static_subtype: Some("form-data"),
170-
parameters: None,
171+
params: None,
171172
};

src/mime/mod.rs

Lines changed: 94 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod parse;
77

88
pub use constants::*;
99

10+
use std::borrow::Cow;
1011
use std::collections::HashMap;
1112
use std::fmt::{self, Debug, Display};
1213
use std::option;
@@ -27,7 +28,7 @@ pub struct Mime {
2728
pub(crate) static_essence: Option<&'static str>,
2829
pub(crate) static_basetype: Option<&'static str>,
2930
pub(crate) static_subtype: Option<&'static str>,
30-
pub(crate) parameters: Option<HashMap<String, String>>,
31+
pub(crate) params: Option<ParamKind>,
3132
}
3233

3334
impl Mime {
@@ -46,7 +47,7 @@ impl Mime {
4647
subtype: String::new(), // TODO: fill in.
4748
static_basetype: None, // TODO: fill in
4849
static_subtype: None,
49-
parameters: None, // TODO: fill in.
50+
params: None, // TODO: fill in.
5051
})
5152
}
5253

@@ -81,13 +82,18 @@ impl Mime {
8182
}
8283

8384
/// Get a reference to a param.
84-
pub fn param(&self, s: &str) -> Option<&String> {
85-
self.parameters.as_ref().map(|hm| hm.get(s)).flatten()
86-
}
87-
88-
/// Get a mutable reference to a param.
89-
pub fn param_mut(&mut self, s: &str) -> Option<&mut String> {
90-
self.parameters.as_mut().map(|hm| hm.get_mut(s)).flatten()
85+
pub fn param(&self, name: impl Into<ParamName>) -> Option<&ParamValue> {
86+
let name: ParamName = name.into();
87+
self.params
88+
.as_ref()
89+
.map(|inner| match inner {
90+
ParamKind::Map(hm) => hm.get(&name),
91+
ParamKind::Utf8 => match name {
92+
ParamName(Cow::Borrowed("charset")) => Some(&ParamValue(Cow::Borrowed("utf8"))),
93+
_ => None,
94+
},
95+
})
96+
.flatten()
9197
}
9298
}
9399

@@ -98,13 +104,18 @@ impl Display for Mime {
98104
} else {
99105
write!(f, "{}", &self.essence)?
100106
}
101-
if let Some(parameters) = &self.parameters {
102-
assert!(!parameters.is_empty());
103-
write!(f, "; ")?;
104-
for (i, (key, value)) in parameters.iter().enumerate() {
105-
write!(f, "{}={}", key, value)?;
106-
if i != parameters.len() - 1 {
107-
write!(f, ",")?;
107+
if let Some(params) = &self.params {
108+
match params {
109+
ParamKind::Utf8 => write!(f, "; charset=utf-8")?,
110+
ParamKind::Map(params) => {
111+
assert!(!params.is_empty());
112+
write!(f, "; ")?;
113+
for (i, (key, value)) in params.iter().enumerate() {
114+
write!(f, "{}={}", key, value)?;
115+
if i != params.len() - 1 {
116+
write!(f, ",")?;
117+
}
118+
}
108119
}
109120
}
110121
}
@@ -137,7 +148,7 @@ impl FromStr for Mime {
137148
subtype: String::new(), // TODO: fill in.
138149
static_basetype: None, // TODO: fill in
139150
static_subtype: None, // TODO: fill in
140-
parameters: None, // TODO: fill in.
151+
params: None, // TODO: fill in.
141152
})
142153
}
143154
}
@@ -153,3 +164,69 @@ impl ToHeaderValues for Mime {
153164
Ok(header.to_header_values().unwrap())
154165
}
155166
}
167+
/// A parameter name.
168+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
169+
pub struct ParamName(Cow<'static, str>);
170+
171+
impl Display for ParamName {
172+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
173+
Display::fmt(&self.0, f)
174+
}
175+
}
176+
177+
impl FromStr for ParamName {
178+
type Err = crate::Error;
179+
180+
/// Create a new `HeaderName`.
181+
///
182+
/// This checks it's valid ASCII, and lowercases it.
183+
fn from_str(s: &str) -> Result<Self, Self::Err> {
184+
crate::ensure!(s.is_ascii(), "String slice should be valid ASCII");
185+
Ok(ParamName(Cow::Owned(s.to_ascii_lowercase())))
186+
}
187+
}
188+
189+
impl<'a> From<&'a str> for ParamName {
190+
fn from(value: &'a str) -> Self {
191+
Self::from_str(value).unwrap()
192+
}
193+
}
194+
195+
/// A parameter value.
196+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
197+
pub struct ParamValue(Cow<'static, str>);
198+
199+
impl Display for ParamValue {
200+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
201+
Display::fmt(&self.0, f)
202+
}
203+
}
204+
205+
impl<'a> PartialEq<&'a str> for ParamValue {
206+
fn eq(&self, other: &&'a str) -> bool {
207+
&self.0 == other
208+
}
209+
}
210+
211+
impl PartialEq<str> for ParamValue {
212+
fn eq(&self, other: &str) -> bool {
213+
&self.0 == other
214+
}
215+
}
216+
217+
/// This is a hack that allows us to mark a trait as utf8 during compilation. We
218+
/// can remove this once we can construct HashMap during compilation.
219+
#[derive(Debug, Clone)]
220+
pub(crate) enum ParamKind {
221+
Utf8,
222+
Map(HashMap<ParamName, ParamValue>),
223+
}
224+
225+
impl ParamKind {
226+
pub(crate) fn unwrap(&mut self) -> &mut HashMap<ParamName, ParamValue> {
227+
match self {
228+
Self::Map(t) => t,
229+
_ => panic!("Unwrapped a ParamKind::utf8"),
230+
}
231+
}
232+
}

src/mime/parse.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::collections::HashMap;
88
use std::io::prelude::*;
99
use std::io::Cursor;
1010

11-
use super::Mime;
11+
use super::{Mime, ParamKind, ParamName, ParamValue};
1212

1313
/// Parse a string into a mime type.
1414
#[allow(dead_code)]
@@ -54,13 +54,13 @@ pub(crate) fn parse(s: &str) -> crate::Result<Mime> {
5454
essence: format!("{}/{}", &basetype, &subtype),
5555
basetype,
5656
subtype,
57-
parameters: None,
57+
params: None,
5858
static_essence: None,
5959
static_basetype: None,
6060
static_subtype: None,
6161
};
6262

63-
// parse parameters into a hashmap
63+
// parse params into a hashmap
6464
//
6565
// ```txt
6666
// text/html; charset=utf-8;
@@ -121,12 +121,14 @@ pub(crate) fn parse(s: &str) -> crate::Result<Mime> {
121121
param_value.make_ascii_lowercase();
122122

123123
// Insert attribute pair into hashmap.
124-
mime.parameters.get_or_insert_with(HashMap::new);
124+
mime.params
125+
.get_or_insert_with(|| ParamKind::Map(HashMap::new()));
125126

126-
mime.parameters
127+
mime.params
127128
.as_mut()
128129
.unwrap()
129-
.insert(param_name, param_value);
130+
.unwrap()
131+
.insert(ParamName(param_name.into()), ParamValue(param_value.into()));
130132
}
131133

132134
Ok(mime)
@@ -171,12 +173,12 @@ fn test() {
171173
let mime = parse("text/html; charset=utf-8").unwrap();
172174
assert_eq!(mime.basetype(), "text");
173175
assert_eq!(mime.subtype(), "html");
174-
assert_eq!(mime.param("charset"), Some(&"utf-8".to_string()));
176+
assert_eq!(mime.param("charset").unwrap(), "utf-8");
175177

176178
let mime = parse("text/html; charset=utf-8;").unwrap();
177179
assert_eq!(mime.basetype(), "text");
178180
assert_eq!(mime.subtype(), "html");
179-
assert_eq!(mime.param("charset"), Some(&"utf-8".to_string()));
181+
assert_eq!(mime.param("charset").unwrap(), "utf-8");
180182

181183
assert!(parse("text").is_err());
182184
assert!(parse("text/").is_err());

0 commit comments

Comments
 (0)