Skip to content

Commit c4a664e

Browse files
committed
Merge remote-tracking branch 'original/master'
2 parents c555ab1 + 24bd096 commit c4a664e

File tree

8 files changed

+132
-113
lines changed

8 files changed

+132
-113
lines changed

Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "async-h1"
3-
version = "2.0.0"
3+
version = "2.0.2"
44
license = "MIT OR Apache-2.0"
55
repository = "https://github.com/http-rs/async-h1"
66
documentation = "https://docs.rs/async-h1"
@@ -12,7 +12,6 @@ readme = "README.md"
1212
edition = "2018"
1313

1414
[dependencies]
15-
url = "2.1.0"
1615
httparse = "1.3.3"
1716
async-std = { version = "1.6.0", features = ["unstable"] }
1817
http-types = "2.0.0"

src/server/decode.rs

Lines changed: 89 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use std::str::FromStr;
44

55
use async_std::io::{BufReader, Read, Write};
66
use async_std::prelude::*;
7-
use http_types::headers::{CONTENT_LENGTH, EXPECT, HOST, TRANSFER_ENCODING};
7+
use http_types::headers::{CONTENT_LENGTH, EXPECT, TRANSFER_ENCODING};
88
use http_types::{ensure, ensure_eq, format_err};
9-
use http_types::{Body, Method, Request};
9+
use http_types::{Body, Method, Request, Url};
1010

1111
use crate::chunked::ChunkedDecoder;
1212
use crate::{MAX_HEADERS, MAX_HEAD_LENGTH};
@@ -56,9 +56,6 @@ where
5656
let method = httparse_req.method;
5757
let method = method.ok_or_else(|| format_err!("No method found"))?;
5858

59-
let path = httparse_req.path;
60-
let path = path.ok_or_else(|| format_err!("No uri found"))?;
61-
6259
let version = httparse_req.version;
6360
let version = version.ok_or_else(|| format_err!("No version found"))?;
6461

@@ -69,16 +66,14 @@ where
6966
version
7067
);
7168

72-
let mut req = Request::new(
73-
Method::from_str(method)?,
74-
url::Url::parse("http://_").unwrap().join(path)?,
75-
);
69+
let url = url_from_httparse_req(&httparse_req)?;
70+
71+
let mut req = Request::new(Method::from_str(method)?, url);
7672

7773
for header in httparse_req.headers.iter() {
7874
req.insert_header(header.name, std::str::from_utf8(header.value)?);
7975
}
8076

81-
set_url_and_port_from_host_header(&mut req)?;
8277
handle_100_continue(&req, &mut io).await?;
8378

8479
let content_length = req.header(CONTENT_LENGTH);
@@ -109,29 +104,31 @@ where
109104
Ok(Some(req))
110105
}
111106

112-
fn set_url_and_port_from_host_header(req: &mut Request) -> http_types::Result<()> {
107+
fn url_from_httparse_req(req: &httparse::Request<'_, '_>) -> http_types::Result<Url> {
108+
let path = req.path.ok_or_else(|| format_err!("No uri found"))?;
113109
let host = req
114-
.header(HOST)
115-
.map(|header| header.last()) // There must only exactly one Host header, so this is permissive
116-
.ok_or_else(|| format_err!("Mandatory Host header missing"))? // https://tools.ietf.org/html/rfc7230#section-5.4
117-
.to_string();
118-
119-
if !req.url().cannot_be_a_base() {
120-
if let Some(colon) = host.find(":") {
121-
req.url_mut().set_host(Some(&host[0..colon]))?;
122-
req.url_mut()
123-
.set_port(host[colon + 1..].parse().ok())
124-
.unwrap();
125-
} else {
126-
req.url_mut().set_host(Some(&host))?;
127-
}
110+
.headers
111+
.iter()
112+
.filter(|x| x.name.eq_ignore_ascii_case("host"))
113+
.next()
114+
.ok_or_else(|| format_err!("Mandatory Host header missing"))?
115+
.value;
116+
117+
let host = std::str::from_utf8(host)?;
118+
119+
if path.starts_with("http://") || path.starts_with("https://") {
120+
Ok(Url::parse(path)?)
121+
} else if path.starts_with("/") {
122+
Ok(Url::parse(&format!("http://{}/", host))?.join(path)?)
123+
} else if req.method.unwrap().eq_ignore_ascii_case("connect") {
124+
Ok(Url::parse(&format!("http://{}/", path))?)
125+
} else {
126+
Err(format_err!("unexpected uri format"))
128127
}
129-
130-
Ok(())
131128
}
132129

133130
const EXPECT_HEADER_VALUE: &str = "100-continue";
134-
const EXPECT_RESPONSE: &[u8] = b"HTTP/1.1 100 Continue\r\n";
131+
const EXPECT_RESPONSE: &[u8] = b"HTTP/1.1 100 Continue\r\n\r\n";
135132

136133
async fn handle_100_continue<IO>(req: &Request, io: &mut IO) -> http_types::Result<()>
137134
where
@@ -148,103 +145,96 @@ where
148145
mod tests {
149146
use super::*;
150147

151-
#[test]
152-
fn handle_100_continue_does_nothing_with_no_expect_header() {
153-
let request = Request::new(Method::Get, url::Url::parse("x:").unwrap());
154-
let mut io = async_std::io::Cursor::new(vec![]);
155-
let result = async_std::task::block_on(handle_100_continue(&request, &mut io));
156-
assert_eq!(std::str::from_utf8(&io.into_inner()).unwrap(), "");
157-
assert!(result.is_ok());
148+
fn httparse_req(buf: &str, f: impl Fn(httparse::Request<'_, '_>)) {
149+
let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS];
150+
let mut res = httparse::Request::new(&mut headers[..]);
151+
res.parse(buf.as_bytes()).unwrap();
152+
f(res)
158153
}
159154

160155
#[test]
161-
fn handle_100_continue_sends_header_if_expects_is_exactly_right() {
162-
let mut request = Request::new(Method::Get, url::Url::parse("x:").unwrap());
163-
request.append_header("expect", "100-continue");
164-
let mut io = async_std::io::Cursor::new(vec![]);
165-
let result = async_std::task::block_on(handle_100_continue(&request, &mut io));
166-
assert_eq!(
167-
std::str::from_utf8(&io.into_inner()).unwrap(),
168-
"HTTP/1.1 100 Continue\r\n"
156+
fn url_for_connect() {
157+
httparse_req(
158+
"CONNECT server.example.com:443 HTTP/1.1\r\nHost: server.example.com:443\r\n",
159+
|req| {
160+
let url = url_from_httparse_req(&req).unwrap();
161+
assert_eq!(url.as_str(), "http://server.example.com:443/");
162+
},
169163
);
170-
assert!(result.is_ok());
171164
}
172165

173166
#[test]
174-
fn handle_100_continue_does_nothing_if_expects_header_is_wrong() {
175-
let mut request = Request::new(Method::Get, url::Url::parse("x:").unwrap());
176-
request.append_header("expect", "110-extensions-not-allowed");
177-
let mut io = async_std::io::Cursor::new(vec![]);
178-
let result = async_std::task::block_on(handle_100_continue(&request, &mut io));
179-
assert_eq!(std::str::from_utf8(&io.into_inner()).unwrap(), "");
180-
assert!(result.is_ok());
167+
fn url_for_host_plus_path() {
168+
httparse_req(
169+
"GET /some/resource HTTP/1.1\r\nHost: server.example.com:443\r\n",
170+
|req| {
171+
let url = url_from_httparse_req(&req).unwrap();
172+
assert_eq!(url.as_str(), "http://server.example.com:443/some/resource");
173+
},
174+
)
181175
}
182176

183177
#[test]
184-
fn test_setting_host_with_no_port() {
185-
let mut request = request_with_host_header("subdomain.mydomain.tld");
186-
set_url_and_port_from_host_header(&mut request).unwrap();
187-
assert_eq!(
188-
request.url(),
189-
&url::Url::parse("http://subdomain.mydomain.tld/some/path").unwrap()
190-
);
178+
fn url_for_host_plus_absolute_url() {
179+
httparse_req(
180+
"GET http://domain.com/some/resource HTTP/1.1\r\nHost: server.example.com\r\n",
181+
|req| {
182+
let url = url_from_httparse_req(&req).unwrap();
183+
assert_eq!(url.as_str(), "http://domain.com/some/resource"); // host header MUST be ignored according to spec
184+
},
185+
)
191186
}
192187

193188
#[test]
194-
fn test_setting_host_with_a_port() {
195-
let mut request = request_with_host_header("subdomain.mydomain.tld:8080");
196-
set_url_and_port_from_host_header(&mut request).unwrap();
197-
assert_eq!(
198-
request.url(),
199-
&url::Url::parse("http://subdomain.mydomain.tld:8080/some/path").unwrap()
200-
);
189+
fn url_for_conflicting_connect() {
190+
httparse_req(
191+
"CONNECT server.example.com:443 HTTP/1.1\r\nHost: conflicting.host\r\n",
192+
|req| {
193+
let url = url_from_httparse_req(&req).unwrap();
194+
assert_eq!(url.as_str(), "http://server.example.com:443/");
195+
},
196+
)
201197
}
202198

203199
#[test]
204-
fn test_setting_host_with_an_ip_and_port() {
205-
let mut request = request_with_host_header("12.34.56.78:90");
206-
set_url_and_port_from_host_header(&mut request).unwrap();
207-
assert_eq!(
208-
request.url(),
209-
&url::Url::parse("http://12.34.56.78:90/some/path").unwrap()
210-
);
200+
fn url_for_malformed_resource_path() {
201+
httparse_req(
202+
"GET not-a-url HTTP/1.1\r\nHost: server.example.com\r\n",
203+
|req| {
204+
assert!(url_from_httparse_req(&req).is_err());
205+
},
206+
)
211207
}
212208

213209
#[test]
214-
fn test_malformed_nonnumeric_port_is_ignored() {
215-
let mut request = request_with_host_header("hello.world:uh-oh");
216-
set_url_and_port_from_host_header(&mut request).unwrap();
217-
assert_eq!(
218-
request.url(),
219-
&url::Url::parse("http://hello.world/some/path").unwrap()
220-
);
210+
fn handle_100_continue_does_nothing_with_no_expect_header() {
211+
let request = Request::new(Method::Get, Url::parse("x:").unwrap());
212+
let mut io = async_std::io::Cursor::new(vec![]);
213+
let result = async_std::task::block_on(handle_100_continue(&request, &mut io));
214+
assert_eq!(std::str::from_utf8(&io.into_inner()).unwrap(), "");
215+
assert!(result.is_ok());
221216
}
222217

223218
#[test]
224-
fn test_malformed_trailing_colon_is_ignored() {
225-
let mut request = request_with_host_header("edge.cases:");
226-
set_url_and_port_from_host_header(&mut request).unwrap();
219+
fn handle_100_continue_sends_header_if_expects_is_exactly_right() {
220+
let mut request = Request::new(Method::Get, Url::parse("x:").unwrap());
221+
request.append_header("expect", "100-continue");
222+
let mut io = async_std::io::Cursor::new(vec![]);
223+
let result = async_std::task::block_on(handle_100_continue(&request, &mut io));
227224
assert_eq!(
228-
request.url(),
229-
&url::Url::parse("http://edge.cases/some/path").unwrap()
225+
std::str::from_utf8(&io.into_inner()).unwrap(),
226+
"HTTP/1.1 100 Continue\r\n\r\n"
230227
);
228+
assert!(result.is_ok());
231229
}
232230

233231
#[test]
234-
fn test_malformed_leading_colon_is_invalid_host_value() {
235-
let mut request = request_with_host_header(":300");
236-
assert!(set_url_and_port_from_host_header(&mut request).is_err());
237-
}
238-
239-
#[test]
240-
fn test_malformed_invalid_url_host_is_invalid_host_header_value() {
241-
let mut request = request_with_host_header(" ");
242-
assert!(set_url_and_port_from_host_header(&mut request).is_err());
243-
}
244-
245-
fn request_with_host_header(host: &str) -> Request {
246-
let mut req = Request::new(Method::Get, url::Url::parse("http://_/some/path").unwrap());
247-
req.insert_header(HOST, host);
248-
req
232+
fn handle_100_continue_does_nothing_if_expects_header_is_wrong() {
233+
let mut request = Request::new(Method::Get, Url::parse("x:").unwrap());
234+
request.append_header("expect", "110-extensions-not-allowed");
235+
let mut io = async_std::io::Cursor::new(vec![]);
236+
let result = async_std::task::block_on(handle_100_continue(&request, &mut io));
237+
assert_eq!(std::str::from_utf8(&io.into_inner()).unwrap(), "");
238+
assert!(result.is_ok());
249239
}
250240
}

src/server/encode.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use async_std::io;
66
use async_std::io::prelude::*;
77
use async_std::task::{Context, Poll};
88
use http_types::headers::{CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
9-
use http_types::Response;
9+
use http_types::{Method, Response};
1010

1111
use crate::chunked::ChunkedEncoder;
1212
use crate::date::fmt_http_date;
@@ -36,6 +36,8 @@ pub struct Encoder {
3636
body_bytes_written: usize,
3737
/// An encoder for chunked encoding.
3838
chunked: ChunkedEncoder,
39+
/// the http method that this response is in reply to
40+
method: Method,
3941
}
4042

4143
#[derive(Debug)]
@@ -80,6 +82,7 @@ impl Encoder {
8082
body_len: 0,
8183
body_bytes_written: 0,
8284
chunked: ChunkedEncoder::new(),
85+
method,
8386
}
8487
}
8588

@@ -97,7 +100,7 @@ impl Encoder {
97100
match self.state {
98101
Start => assert!(matches!(state, ComputeHead)),
99102
ComputeHead => assert!(matches!(state, EncodeHead)),
100-
EncodeHead => assert!(matches!(state, EncodeChunkedBody | EncodeFixedBody)),
103+
EncodeHead => assert!(matches!(state, EncodeChunkedBody | EncodeFixedBody | End)),
101104
EncodeFixedBody => assert!(matches!(state, End)),
102105
EncodeChunkedBody => assert!(matches!(state, End)),
103106
End => panic!("No state transitions allowed after the ServerEncoder has ended"),
@@ -176,14 +179,20 @@ impl Encoder {
176179
// If we've read the total length of the head we're done
177180
// reading the head and can transition to reading the body
178181
if self.head_bytes_written == head_len {
179-
// The response length lets us know if we are encoding
180-
// our body in chunks or not
181-
match self.res.len() {
182-
Some(body_len) => {
183-
self.body_len = body_len;
184-
self.dispatch(State::EncodeFixedBody, cx, buf)
182+
if self.method == Method::Head {
183+
// If we are responding to a HEAD request, we MUST NOT send
184+
// body content
185+
self.dispatch(State::End, cx, buf)
186+
} else {
187+
// The response length lets us know if we are encoding
188+
// our body in chunks or not
189+
match self.res.len() {
190+
Some(body_len) => {
191+
self.body_len = body_len;
192+
self.dispatch(State::EncodeFixedBody, cx, buf)
193+
}
194+
None => self.dispatch(State::EncodeChunkedBody, cx, buf),
185195
}
186-
None => self.dispatch(State::EncodeChunkedBody, cx, buf),
187196
}
188197
} else {
189198
// If we haven't read the entire header it means `buf` isn't

src/server/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,11 @@ where
7070
}
7171
};
7272

73+
let method = req.method();
7374
// Pass the request to the endpoint and encode the response.
7475
let res = endpoint(req).await?;
75-
let mut encoder = Encoder::new(res);
76+
77+
let mut encoder = Encoder::new(res, method);
7678

7779
// Stream the response to the writer.
7880
io::copy(&mut encoder, &mut io).await?;

tests/client.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use crate::common::fixture_path;
22
use async_h1::client;
33
use async_std::fs::File;
4-
use http_types::{headers, Method, Request, StatusCode};
5-
use url::Url;
4+
use http_types::{headers, Method, Request, StatusCode, Url};
65

76
mod common;
87

tests/fixtures/head_request.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
HEAD / HTTP/1.1
2+
host: example.com
3+
user-agent: curl/7.54.0
4+

tests/fixtures/head_response.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
HTTP/1.1 200 OK
2+
content-length: 5
3+
date: {DATE}
4+
content-type: text/plain;charset=utf-8
5+

tests/server.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,14 @@ async fn test_invalid_trailer() {
144144

145145
assert!(case.read_result().await.is_empty());
146146
}
147+
#[async_std::test]
148+
async fn empty_body_for_head_requests() {
149+
let case =
150+
TestCase::new_server("fixtures/head_request.txt", "fixtures/head_response.txt").await;
151+
152+
async_h1::accept(case.clone(), |_| async { Ok("hello".into()) })
153+
.await
154+
.unwrap();
155+
156+
case.assert().await;
157+
}

0 commit comments

Comments
 (0)