Skip to content

Commit b363229

Browse files
committed
Support non-utf8 headers
Signed-off-by: Ryan Levick <[email protected]>
1 parent cfcec30 commit b363229

File tree

2 files changed

+119
-45
lines changed

2 files changed

+119
-45
lines changed

sdk/rust/src/http.rs

Lines changed: 103 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/// Traits for converting between the various types
22
pub mod conversions;
33

4+
use std::collections::HashMap;
5+
46
#[doc(inline)]
57
pub use conversions::IntoResponse;
68

@@ -21,17 +23,32 @@ pub struct Request {
2123
/// The first item is set to `None` if the supplied uri is malformed
2224
uri: (Option<hyperium::Uri>, String),
2325
/// The request headers
24-
headers: Vec<(String, String)>,
26+
headers: HashMap<String, HeaderValue>,
2527
/// The request body as bytes
2628
body: Vec<u8>,
2729
}
2830

31+
enum HeaderValue {
32+
String(String),
33+
Bytes(Vec<u8>),
34+
}
35+
36+
impl HeaderValue {
37+
/// Turn the `HeaderValue` into bytes
38+
fn into_bytes(self) -> Vec<u8> {
39+
match self {
40+
HeaderValue::String(s) => s.into_bytes(),
41+
HeaderValue::Bytes(b) => b,
42+
}
43+
}
44+
}
45+
2946
impl Request {
3047
fn new(method: Method, uri: impl Into<String>) -> Self {
3148
Self {
3249
method,
3350
uri: Self::parse_uri(uri.into()),
34-
headers: Vec::new(),
51+
headers: HashMap::new(),
3552
body: Vec::new(),
3653
}
3754
}
@@ -61,13 +78,33 @@ impl Request {
6178
}
6279

6380
/// The request headers
64-
pub fn headers(&self) -> &[(String, String)] {
65-
&self.headers
81+
///
82+
/// This only returns headers that are utf8 encoded
83+
pub fn headers(&self) -> impl Iterator<Item = (&str, &str)> {
84+
self.headers.iter().filter_map(|(k, v)| match v {
85+
HeaderValue::String(v) => Some((k.as_str(), v.as_str())),
86+
HeaderValue::Bytes(_) => None,
87+
})
6688
}
6789

68-
/// The request headers
69-
pub fn headers_mut(&mut self) -> &mut Vec<(String, String)> {
70-
&mut self.headers
90+
/// Return a header value
91+
///
92+
/// Will return `None` if the header does not exist or if it is not utf8
93+
pub fn header(&self, name: &str) -> Option<&str> {
94+
self.headers
95+
.get(&name.to_lowercase())
96+
.and_then(|v| match v {
97+
HeaderValue::String(s) => Some(s.as_str()),
98+
HeaderValue::Bytes(_) => None,
99+
})
100+
}
101+
102+
/// The request headers as bytes
103+
pub fn headers_raw(&self) -> impl Iterator<Item = (&str, &[u8])> {
104+
self.headers.iter().map(|(k, v)| match v {
105+
HeaderValue::String(v) => (k.as_str(), v.as_bytes()),
106+
HeaderValue::Bytes(v) => (k.as_str(), v.as_slice()),
107+
})
71108
}
72109

73110
/// The request body
@@ -127,7 +164,15 @@ impl RequestBuilder {
127164

128165
/// Set the headers
129166
pub fn headers(&mut self, headers: impl conversions::IntoHeaders) -> &mut Self {
130-
self.request.headers = headers.into_headers();
167+
self.request.headers = into_header_rep(headers);
168+
self
169+
}
170+
171+
/// Set a header
172+
pub fn header(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
173+
self.request
174+
.headers
175+
.insert(key.into().to_lowercase(), HeaderValue::String(value.into()));
131176
self
132177
}
133178

@@ -151,7 +196,7 @@ pub struct Response {
151196
/// The status of the response
152197
status: StatusCode,
153198
/// The response headers
154-
headers: Vec<(String, String)>,
199+
headers: HashMap<String, HeaderValue>,
155200
/// The body of the response as bytes
156201
body: Vec<u8>,
157202
}
@@ -161,7 +206,7 @@ impl Response {
161206
pub fn new(status: impl conversions::IntoStatusCode, body: impl conversions::IntoBody) -> Self {
162207
Self {
163208
status: status.into_status_code(),
164-
headers: Vec::new(),
209+
headers: HashMap::new(),
165210
body: body.into_body(),
166211
}
167212
}
@@ -173,16 +218,30 @@ impl Response {
173218

174219
/// The response headers
175220
///
176-
/// Technically headers do not have to be utf8 encoded but this type assumes they are.
177-
/// If you know you'll be dealing with non-utf8 encoded headers, reach more a more powerful
178-
/// response type like that found in the `http` crate.
179-
pub fn headers(&self) -> &[(String, String)] {
180-
&self.headers
221+
/// This only returns headers that are utf8 encoded
222+
pub fn headers(&self) -> impl Iterator<Item = (&str, &str)> {
223+
self.headers.iter().filter_map(|(k, v)| match v {
224+
HeaderValue::String(v) => Some((k.as_str(), v.as_str())),
225+
HeaderValue::Bytes(_) => None,
226+
})
181227
}
182228

183-
/// The response headers
184-
pub fn headers_mut(&mut self) -> &mut Vec<(String, String)> {
185-
&mut self.headers
229+
/// Return a header value
230+
///
231+
/// Will return `None` if the header does not exist or if it is not utf8
232+
pub fn header(&self, name: &str) -> Option<&str> {
233+
self.headers.get(name).and_then(|v| match v {
234+
HeaderValue::String(s) => Some(s.as_str()),
235+
HeaderValue::Bytes(_) => None,
236+
})
237+
}
238+
239+
/// The request headers as bytes
240+
pub fn headers_raw(&self) -> impl Iterator<Item = (&str, &[u8])> {
241+
self.headers.iter().map(|(k, v)| match v {
242+
HeaderValue::String(v) => (k.as_str(), v.as_bytes()),
243+
HeaderValue::Bytes(v) => (k.as_str(), v.as_slice()),
244+
})
186245
}
187246

188247
/// The response body
@@ -205,11 +264,13 @@ impl Response {
205264
}
206265
}
207266

208-
struct ResponseBuilder {
267+
/// A builder for `Response``
268+
pub struct ResponseBuilder {
209269
response: Response,
210270
}
211271

212272
impl ResponseBuilder {
273+
/// Create a new `ResponseBuilder`
213274
pub fn new(status: impl conversions::IntoStatusCode) -> Self {
214275
ResponseBuilder {
215276
response: Response::new(status, Vec::new()),
@@ -224,7 +285,15 @@ impl ResponseBuilder {
224285

225286
/// Set the headers
226287
pub fn headers(&mut self, headers: impl conversions::IntoHeaders) -> &mut Self {
227-
self.response.headers = headers.into_headers();
288+
self.response.headers = into_header_rep(headers.into_headers());
289+
self
290+
}
291+
292+
/// Set a header
293+
pub fn header(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
294+
self.response
295+
.headers
296+
.insert(key.into().to_lowercase(), HeaderValue::String(value.into()));
228297
self
229298
}
230299

@@ -234,11 +303,25 @@ impl ResponseBuilder {
234303
self
235304
}
236305

306+
/// Build the `Response`
237307
pub fn build(&mut self) -> Response {
238308
std::mem::replace(&mut self.response, Response::new(200, Vec::new()))
239309
}
240310
}
241311

312+
fn into_header_rep(headers: impl conversions::IntoHeaders) -> HashMap<String, HeaderValue> {
313+
headers
314+
.into_headers()
315+
.into_iter()
316+
.map(|(k, v)| {
317+
let v = String::from_utf8(v)
318+
.map(HeaderValue::String)
319+
.unwrap_or_else(|e| HeaderValue::Bytes(e.into_bytes()));
320+
(k.to_lowercase(), v)
321+
})
322+
.collect()
323+
}
324+
242325
impl std::hash::Hash for Method {
243326
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
244327
core::mem::discriminant(self).hash(state);

sdk/rust/src/http/conversions.rs

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ impl<B: TryFromBody> TryNonRequestFromRequest for hyperium::Request<B> {
161161
.uri(req.uri())
162162
.method(req.method);
163163
for (n, v) in req.headers {
164-
builder = builder.header(n, v);
164+
builder = builder.header(n, v.into_bytes());
165165
}
166166
Ok(builder.body(B::try_from_body(req.body)?).unwrap())
167167
}
@@ -296,61 +296,52 @@ impl IntoStatusCode for hyperium::StatusCode {
296296

297297
/// A trait for any type that can be turned into `Response` headers
298298
pub trait IntoHeaders {
299-
/// Turn `self` into a `Response` body
300-
fn into_headers(self) -> Vec<(String, String)>;
299+
/// Turn `self` into `Response` headers
300+
fn into_headers(self) -> Vec<(String, Vec<u8>)>;
301301
}
302302

303303
impl IntoHeaders for Vec<(String, String)> {
304-
fn into_headers(self) -> Vec<(String, String)> {
305-
self
304+
fn into_headers(self) -> Vec<(String, Vec<u8>)> {
305+
self.into_iter().map(|(k, v)| (k, v.into_bytes())).collect()
306306
}
307307
}
308308

309309
impl IntoHeaders for Vec<(String, Vec<u8>)> {
310-
fn into_headers(self) -> Vec<(String, String)> {
311-
self.into_iter()
312-
.map(|(k, v)| (k, String::from_utf8_lossy(&v).into_owned()))
313-
.collect()
310+
fn into_headers(self) -> Vec<(String, Vec<u8>)> {
311+
self
314312
}
315313
}
316314

317315
impl IntoHeaders for HashMap<String, Vec<String>> {
318-
fn into_headers(self) -> Vec<(String, String)> {
316+
fn into_headers(self) -> Vec<(String, Vec<u8>)> {
319317
self.into_iter()
320-
.flat_map(|(k, values)| values.into_iter().map(move |v| (k.clone(), v)))
318+
.flat_map(|(k, values)| values.into_iter().map(move |v| (k.clone(), v.into_bytes())))
321319
.collect()
322320
}
323321
}
324322

325323
impl IntoHeaders for HashMap<String, String> {
326-
fn into_headers(self) -> Vec<(String, String)> {
327-
self.into_iter().collect()
324+
fn into_headers(self) -> Vec<(String, Vec<u8>)> {
325+
self.into_iter().map(|(k, v)| (k, v.into_bytes())).collect()
328326
}
329327
}
330328

331329
impl IntoHeaders for HashMap<String, Vec<u8>> {
332-
fn into_headers(self) -> Vec<(String, String)> {
333-
self.into_iter()
334-
.map(|(k, v)| (k, String::from_utf8_lossy(&v).into_owned()))
335-
.collect()
330+
fn into_headers(self) -> Vec<(String, Vec<u8>)> {
331+
self.into_iter().collect()
336332
}
337333
}
338334

339335
impl IntoHeaders for &hyperium::HeaderMap {
340-
fn into_headers(self) -> Vec<(String, String)> {
336+
fn into_headers(self) -> Vec<(String, Vec<u8>)> {
341337
self.iter()
342-
.map(|(k, v)| {
343-
(
344-
k.as_str().to_owned(),
345-
String::from_utf8_lossy(v.as_bytes()).into_owned(),
346-
)
347-
})
338+
.map(|(k, v)| (k.as_str().to_owned(), v.as_bytes().to_owned()))
348339
.collect()
349340
}
350341
}
351342

352343
impl IntoHeaders for Headers {
353-
fn into_headers(self) -> Vec<(String, String)> {
344+
fn into_headers(self) -> Vec<(String, Vec<u8>)> {
354345
self.entries().into_headers()
355346
}
356347
}

0 commit comments

Comments
 (0)