Skip to content

Commit

Permalink
client bugfix: handle the case when we have already buffered a \r\n\r\n
Browse files Browse the repository at this point in the history
  • Loading branch information
jbr committed Nov 27, 2023
1 parent b6880ff commit 9640306
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 8 deletions.
17 changes: 10 additions & 7 deletions client/src/conn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -695,21 +695,24 @@ impl Conn {
else {
return Err(Error::Closed);
};

let mut len = buffer.len();
let mut search_start = 0;
let finder = Finder::new(b"\r\n\r\n");
loop {
buffer.expand();
let bytes = transport.read(&mut buffer[len..]).await?;

let search_start = len.saturating_sub(3);
len += bytes;

let search = finder.find(&buffer[search_start..len]);

if let Some(index) = search {
buffer.truncate(len);
return Ok(search_start + index + 4);
}

search_start = len.saturating_sub(3);

if bytes == 0 {
if len == 0 {
return Err(Error::Closed);
Expand All @@ -728,11 +731,11 @@ impl Conn {
let head_offset = self.read_head().await?;
let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS];
let mut httparse_res = httparse::Response::new(&mut headers);
if httparse_res
.parse(&self.buffer[..head_offset])?
.is_partial()
{
return Err(Error::PartialHead);
let parse_result = httparse_res.parse(&self.buffer[..head_offset])?;

match parse_result {
httparse::Status::Complete(n) if n == head_offset => {}
_ => return Err(Error::PartialHead),
}

self.status = httparse_res.code.map(|code| code.try_into().unwrap());
Expand Down
78 changes: 77 additions & 1 deletion client/tests/one_hundred_continue.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use async_channel::Sender;
use indoc::formatdoc;
use futures_lite::future;
use indoc::{formatdoc, indoc};
use pretty_assertions::assert_eq;
use std::future::Future;
use test_harness::test;
Expand Down Expand Up @@ -133,6 +134,81 @@ async fn empty_body_no_100_continue() -> TestResult {
Ok(())
}

#[test(harness = harness)]
async fn two_small_continues() -> TestResult {
let (transport, conn_fut) =
test_conn(|client| client.post("http://example.com").with_body("body")).await;
let expected_request = formatdoc! {"
POST / HTTP/1.1\r
Expect: 100-continue\r
User-Agent: {USER_AGENT}\r
Host: example.com\r
Connection: close\r
Content-Length: 4\r
\r
"};

assert_eq!(expected_request, transport.read_available_string().await);

for _ in 0..2 {
transport.write_all("HTTP/1.1 100 Continue\r\n");
future::yield_now().await;
transport.write_all("\r\n");
future::yield_now().await;
}
assert_eq!("body", transport.read_available_string().await);

transport.write_all(formatdoc! {"
HTTP/1.1 200 Ok\r
Date: {TEST_DATE}\r
Connection: close\r
Content-Length: 0\r
\r
"});
let conn = conn_fut.await.unwrap();
assert_eq!(Some(Status::Ok), conn.status());

Ok(())
}

#[test(harness = harness)]
async fn little_continue_big_continue() -> TestResult {
let (transport, conn_fut) =
test_conn(|client| client.post("http://example.com").with_body("body")).await;

let expected_request = formatdoc! {"
POST / HTTP/1.1\r
Expect: 100-continue\r
User-Agent: {USER_AGENT}\r
Host: example.com\r
Connection: close\r
Content-Length: 4\r
\r
"};

assert_eq!(expected_request, transport.read_available_string().await);

transport.write_all(indoc! {"
HTTP/1.1 100 Continue\r
\r
HTTP/1.1 100 Continue\r
X-Filler: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r
\r
"});
assert_eq!("body", transport.read_available_string().await);

transport.write_all(formatdoc! {"
HTTP/1.1 200 Ok\r
Date: {TEST_DATE}\r
Connection: close\r
Content-Length: 0\r
\r
"});
let conn = conn_fut.await.unwrap();
assert_eq!(Some(Status::Ok), conn.status());
Ok(())
}

const TEST_DATE: &'static str = "Tue, 21 Nov 2023 21:27:21 GMT";

struct TestConnector(Sender<TestTransport>);
Expand Down

0 comments on commit 9640306

Please sign in to comment.