Skip to content

Commit d721ff4

Browse files
committed
Enable statically encoded utf-8 in params
1 parent 42c70ac commit d721ff4

File tree

4 files changed

+102
-49
lines changed

4 files changed

+102
-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: 75 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,17 @@ 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, s: &str) -> Option<&ParamValue> {
86+
self.params
87+
.as_ref()
88+
.map(|inner| match inner {
89+
ParamKind::Map(hm) => hm.get(&ParamName(s.to_owned().into())),
90+
ParamKind::Utf8 => match s {
91+
"charset" => Some(&ParamValue(Cow::Borrowed("utf8"))),
92+
_ => None,
93+
},
94+
})
95+
.flatten()
9196
}
9297
}
9398

@@ -98,13 +103,18 @@ impl Display for Mime {
98103
} else {
99104
write!(f, "{}", &self.essence)?
100105
}
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, ",")?;
106+
if let Some(params) = &self.params {
107+
match params {
108+
ParamKind::Utf8 => write!(f, "; charset=utf-8")?,
109+
ParamKind::Map(params) => {
110+
assert!(!params.is_empty());
111+
write!(f, "; ")?;
112+
for (i, (key, value)) in params.iter().enumerate() {
113+
write!(f, "{}={}", key, value)?;
114+
if i != params.len() - 1 {
115+
write!(f, ",")?;
116+
}
117+
}
108118
}
109119
}
110120
}
@@ -137,7 +147,7 @@ impl FromStr for Mime {
137147
subtype: String::new(), // TODO: fill in.
138148
static_basetype: None, // TODO: fill in
139149
static_subtype: None, // TODO: fill in
140-
parameters: None, // TODO: fill in.
150+
params: None, // TODO: fill in.
141151
})
142152
}
143153
}
@@ -153,3 +163,51 @@ impl ToHeaderValues for Mime {
153163
Ok(header.to_header_values().unwrap())
154164
}
155165
}
166+
/// A parameter name.
167+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
168+
pub struct ParamName(Cow<'static, str>);
169+
170+
impl Display for ParamName {
171+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
172+
Display::fmt(&self.0, f)
173+
}
174+
}
175+
176+
/// A parameter value.
177+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
178+
pub struct ParamValue(Cow<'static, str>);
179+
180+
impl Display for ParamValue {
181+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
182+
Display::fmt(&self.0, f)
183+
}
184+
}
185+
186+
impl<'a> PartialEq<&'a str> for ParamValue {
187+
fn eq(&self, other: &&'a str) -> bool {
188+
&self.0 == other
189+
}
190+
}
191+
192+
impl PartialEq<str> for ParamValue {
193+
fn eq(&self, other: &str) -> bool {
194+
&self.0 == other
195+
}
196+
}
197+
198+
/// This is a hack that allows us to mark a trait as utf8 during compilation. We
199+
/// can remove this once we can construct HashMap during compilation.
200+
#[derive(Debug, Clone)]
201+
pub(crate) enum ParamKind {
202+
Utf8,
203+
Map(HashMap<ParamName, ParamValue>),
204+
}
205+
206+
impl ParamKind {
207+
pub(crate) fn unwrap(&mut self) -> &mut HashMap<ParamName, ParamValue> {
208+
match self {
209+
Self::Map(t) => t,
210+
_ => panic!("Unwrapped a ParamKind::utf8"),
211+
}
212+
}
213+
}

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)