├── fuzz ├── .gitignore ├── init_corpus │ └── server_accept │ │ ├── zero_length_no_mime │ │ ├── fixed_length │ │ └── chunked ├── dicts │ └── server_accept ├── Cargo.toml └── fuzz_targets │ └── server_accept.rs ├── rustfmt.toml ├── .gitignore ├── src ├── chunked │ ├── mod.rs │ ├── encoder.rs │ └── decoder.rs ├── client │ ├── mod.rs │ ├── decode.rs │ └── encode.rs ├── body_encoder.rs ├── server │ ├── body_reader.rs │ ├── encode.rs │ ├── mod.rs │ └── decode.rs ├── read_notifier.rs ├── lib.rs └── date.rs ├── .travis.yml ├── run-fuzzer.sh ├── examples ├── client.rs └── server.rs ├── Cargo.toml ├── LICENSE-MIT ├── .github ├── workflows │ └── ci.yaml ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md ├── tests ├── continue.rs ├── client_decode.rs ├── server_decode.rs ├── server_encode.rs ├── client_encode.rs ├── accept.rs ├── test_utils.rs └── server-chunked-encode-large.rs ├── README.md └── LICENSE-APACHE /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | newline_style = "Unix" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | target/ 3 | tmp/ 4 | dist/ 5 | npm-debug.log* 6 | Cargo.lock 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /fuzz/init_corpus/server_accept/zero_length_no_mime: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | content-length: 0 3 | 4 | -------------------------------------------------------------------------------- /fuzz/init_corpus/server_accept/fixed_length: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | content-type: text/plain 3 | content-length: 10 4 | 5 | aaaaabbbbb -------------------------------------------------------------------------------- /src/chunked/mod.rs: -------------------------------------------------------------------------------- 1 | mod decoder; 2 | mod encoder; 3 | 4 | pub(crate) use decoder::ChunkedDecoder; 5 | pub(crate) use encoder::ChunkedEncoder; 6 | -------------------------------------------------------------------------------- /fuzz/init_corpus/server_accept/chunked: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | content-type: text/plain 3 | transfer-encoding: chunked 4 | 5 | 5 6 | Hello 7 | a 8 | aaaaabbbbb 9 | 0 10 | 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | 5 | before_script: | 6 | rustup component add rustfmt-preview && 7 | rustup component add clippy-preview 8 | script: | 9 | cargo fmt -- --check && 10 | cargo clippy -- -D clippy && 11 | cargo build --verbose && 12 | cargo test --verbose 13 | cache: cargo 14 | -------------------------------------------------------------------------------- /run-fuzzer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | TARGET_NAME="$1" 5 | if [ -z "$TARGET_NAME" ]; then 6 | echo "$0: target name required" >&2 7 | exit 1 8 | fi 9 | 10 | mkdir -p "./fuzz/corpus/${TARGET_NAME}/" 11 | cargo +nightly fuzz run "${TARGET_NAME}" \ 12 | "./fuzz/corpus/${TARGET_NAME}/" "./fuzz/init_corpus/${TARGET_NAME}/" -- \ 13 | -dict="./fuzz/dicts/${TARGET_NAME}" \ 14 | -timeout=3 15 | -------------------------------------------------------------------------------- /fuzz/dicts/server_accept: -------------------------------------------------------------------------------- 1 | "HTTP/1.1" 2 | "GET" 3 | "POST" 4 | "PUT" 5 | "DELETE" 6 | "PATCH" 7 | "OPTIONS" 8 | "CONNECT" 9 | "HEAD" 10 | " /" 11 | "index.html" 12 | "?q=" 13 | "content-type" 14 | "transfer-encoding" 15 | "chunked" 16 | "text/plain" 17 | "application/octet-stream" 18 | "application/json" 19 | "image/png" 20 | "audio/opus" 21 | "authorization" 22 | "cookie" 23 | "content-length" 24 | "host" 25 | "Basic" 26 | "accept-encoding" 27 | "gzip" 28 | "br" 29 | "\x0d\x0a" 30 | ": " 31 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "async-h1-fuzz" 4 | version = "0.0.0" 5 | authors = ["Automatically generated"] 6 | publish = false 7 | edition = "2018" 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies] 13 | async-std = "1.5.0" 14 | http-types = "1.0.0" 15 | libfuzzer-sys = "0.3" 16 | futures-io = "0.3" 17 | 18 | [dependencies.async-h1] 19 | path = ".." 20 | 21 | # Prevent this from interfering with workspaces 22 | [workspace] 23 | members = ["."] 24 | 25 | [[bin]] 26 | name = "server_accept" 27 | path = "fuzz_targets/server_accept.rs" 28 | -------------------------------------------------------------------------------- /examples/client.rs: -------------------------------------------------------------------------------- 1 | use async_h1::client; 2 | use async_std::net::TcpStream; 3 | use http_types::{Error, Method, Request, Url}; 4 | 5 | #[async_std::main] 6 | async fn main() -> Result<(), Error> { 7 | let stream = TcpStream::connect("127.0.0.1:8080").await?; 8 | let peer_addr = stream.peer_addr()?; 9 | println!("connecting to {}", peer_addr); 10 | 11 | for i in 0usize..2 { 12 | println!("making request {}/2", i + 1); 13 | let url = Url::parse(&format!("http://{}/foo", peer_addr)).unwrap(); 14 | let req = Request::new(Method::Get, url); 15 | let res = client::connect(stream.clone(), req).await?; 16 | println!("{:?}", res); 17 | } 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /src/client/mod.rs: -------------------------------------------------------------------------------- 1 | //! Process HTTP connections on the client. 2 | 3 | use futures_lite::io::{self, AsyncRead as Read, AsyncWrite as Write}; 4 | use http_types::{Request, Response}; 5 | 6 | mod decode; 7 | mod encode; 8 | 9 | pub use decode::decode; 10 | pub use encode::Encoder; 11 | 12 | /// Opens an HTTP/1.1 connection to a remote host. 13 | pub async fn connect(mut stream: RW, req: Request) -> http_types::Result 14 | where 15 | RW: Read + Write + Send + Sync + Unpin + 'static, 16 | { 17 | let mut req = Encoder::new(req); 18 | log::trace!("> {:?}", &req); 19 | 20 | io::copy(&mut req, &mut stream).await?; 21 | 22 | let res = decode(stream).await?; 23 | log::trace!("< {:?}", &res); 24 | 25 | Ok(res) 26 | } 27 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-h1" 3 | version = "2.3.3" 4 | license = "MIT OR Apache-2.0" 5 | repository = "https://github.com/http-rs/async-h1" 6 | documentation = "https://docs.rs/async-h1" 7 | description = "Asynchronous HTTP 1.1 parser." 8 | keywords = ["async", "http", "stream", "parser", "http1"] 9 | categories = [ 10 | "asynchronous", 11 | "parser-implementations", 12 | "web-programming", 13 | "web-programming::http-client", 14 | "web-programming::http-server" 15 | ] 16 | authors = ["Yoshua Wuyts "] 17 | readme = "README.md" 18 | edition = "2018" 19 | 20 | [dependencies] 21 | async-channel = "1.5.1" 22 | async-dup = "1.2.2" 23 | async-global-executor = "2.3.1" 24 | async-io = "1.13.0" 25 | futures-lite = "1.13.0" 26 | http-types = { version = "2.9.0", default-features = false } 27 | httparse = "1.3.4" 28 | log = "0.4.11" 29 | pin-project = "1.0.2" 30 | 31 | [dev-dependencies] 32 | async-std = { version = "1.7.0", features = ["attributes"] } 33 | pretty_assertions = "0.6.1" 34 | -------------------------------------------------------------------------------- /src/body_encoder.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::pin::Pin; 3 | use std::task::{Context, Poll}; 4 | 5 | use futures_lite::io::AsyncRead as Read; 6 | use http_types::Body; 7 | use pin_project::pin_project; 8 | 9 | use crate::chunked::ChunkedEncoder; 10 | 11 | #[pin_project(project=BodyEncoderProjection)] 12 | #[derive(Debug)] 13 | pub(crate) enum BodyEncoder { 14 | Chunked(#[pin] ChunkedEncoder), 15 | Fixed(#[pin] Body), 16 | } 17 | 18 | impl BodyEncoder { 19 | pub(crate) fn new(body: Body) -> Self { 20 | match body.len() { 21 | Some(_) => Self::Fixed(body), 22 | None => Self::Chunked(ChunkedEncoder::new(body)), 23 | } 24 | } 25 | } 26 | 27 | impl Read for BodyEncoder { 28 | fn poll_read( 29 | self: Pin<&mut Self>, 30 | cx: &mut Context<'_>, 31 | buf: &mut [u8], 32 | ) -> Poll> { 33 | match self.project() { 34 | BodyEncoderProjection::Chunked(encoder) => encoder.poll_read(cx, buf), 35 | BodyEncoderProjection::Fixed(body) => body.poll_read(cx, buf), 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Yoshua Wuyts 4 | Copyright (c) 2016-2018 Michael Tilli (Pyfisch) & `httpdate` contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/server/body_reader.rs: -------------------------------------------------------------------------------- 1 | use crate::chunked::ChunkedDecoder; 2 | use async_dup::{Arc, Mutex}; 3 | use futures_lite::io::{AsyncRead as Read, BufReader, Take}; 4 | use std::{ 5 | fmt::Debug, 6 | io, 7 | pin::Pin, 8 | task::{Context, Poll}, 9 | }; 10 | 11 | pub enum BodyReader { 12 | Chunked(Arc>>>), 13 | Fixed(Arc>>>), 14 | None, 15 | } 16 | 17 | impl Debug for BodyReader { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | match self { 20 | BodyReader::Chunked(_) => f.write_str("BodyReader::Chunked"), 21 | BodyReader::Fixed(_) => f.write_str("BodyReader::Fixed"), 22 | BodyReader::None => f.write_str("BodyReader::None"), 23 | } 24 | } 25 | } 26 | 27 | impl Read for BodyReader { 28 | fn poll_read( 29 | self: Pin<&mut Self>, 30 | cx: &mut Context<'_>, 31 | buf: &mut [u8], 32 | ) -> Poll> { 33 | match &*self { 34 | BodyReader::Chunked(r) => Pin::new(&mut *r.lock()).poll_read(cx, buf), 35 | BodyReader::Fixed(r) => Pin::new(&mut *r.lock()).poll_read(cx, buf), 36 | BodyReader::None => Poll::Ready(Ok(0)), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/server.rs: -------------------------------------------------------------------------------- 1 | use async_std::net::{TcpListener, TcpStream}; 2 | use async_std::prelude::*; 3 | use async_std::task; 4 | use http_types::{Response, StatusCode}; 5 | 6 | #[async_std::main] 7 | async fn main() -> http_types::Result<()> { 8 | // Open up a TCP connection and create a URL. 9 | let listener = TcpListener::bind(("127.0.0.1", 8080)).await?; 10 | let addr = format!("http://{}", listener.local_addr()?); 11 | println!("listening on {}", addr); 12 | 13 | // For each incoming TCP connection, spawn a task and call `accept`. 14 | let mut incoming = listener.incoming(); 15 | while let Some(stream) = incoming.next().await { 16 | let stream = stream?; 17 | task::spawn(async { 18 | if let Err(err) = accept(stream).await { 19 | eprintln!("{}", err); 20 | } 21 | }); 22 | } 23 | Ok(()) 24 | } 25 | 26 | // Take a TCP stream, and convert it into sequential HTTP request / response pairs. 27 | async fn accept(stream: TcpStream) -> http_types::Result<()> { 28 | println!("starting new connection from {}", stream.peer_addr()?); 29 | async_h1::accept(stream.clone(), |_req| async move { 30 | let mut res = Response::new(StatusCode::Ok); 31 | res.insert_header("Content-Type", "text/plain"); 32 | res.set_body("Hello world"); 33 | Ok(res) 34 | }) 35 | .await?; 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - staging 8 | - trying 9 | 10 | env: 11 | RUSTFLAGS: -Dwarnings 12 | 13 | jobs: 14 | build_and_test: 15 | name: Build and test 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | matrix: 19 | os: [ubuntu-latest, macOS-latest] 20 | rust: [nightly] 21 | 22 | steps: 23 | - uses: actions/checkout@master 24 | 25 | - name: Install ${{ matrix.rust }} 26 | uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: ${{ matrix.rust }} 29 | override: true 30 | 31 | - name: check 32 | uses: actions-rs/cargo@v1 33 | with: 34 | command: check 35 | args: --all --bins --examples 36 | 37 | - name: check unstable 38 | uses: actions-rs/cargo@v1 39 | with: 40 | command: check 41 | args: --all --benches --bins --examples --tests 42 | 43 | - name: tests 44 | uses: actions-rs/cargo@v1 45 | with: 46 | command: test 47 | args: --all 48 | 49 | check_fmt_and_docs: 50 | name: Checking fmt, clippy, and docs 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@master 54 | - uses: actions-rs/toolchain@v1 55 | with: 56 | toolchain: nightly 57 | components: rustfmt, clippy 58 | override: true 59 | 60 | - name: clippy 61 | run: cargo clippy --tests --examples -- -D warnings 62 | 63 | - name: fmt 64 | run: cargo fmt --all -- --check 65 | 66 | - name: Docs 67 | run: cargo doc 68 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/server_accept.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | use std::pin::Pin; 5 | use std::sync::{Arc, Mutex}; 6 | use std::task::{Context, Poll}; 7 | 8 | use async_std::io::Cursor; 9 | use futures_io::{AsyncRead, AsyncWrite}; 10 | 11 | #[derive(Clone, Debug)] 12 | struct RwWrapper(Arc>>>); 13 | 14 | impl RwWrapper { 15 | fn new(input: Vec) -> Self { 16 | Self(Arc::new(Mutex::new(Cursor::new(input)))) 17 | } 18 | } 19 | 20 | impl AsyncRead for RwWrapper { 21 | fn poll_read( 22 | mut self: Pin<&mut Self>, 23 | cx: &mut Context<'_>, 24 | buf: &mut [u8], 25 | ) -> Poll> { 26 | Pin::new(&mut *self.0.lock().unwrap()).poll_read(cx, buf) 27 | } 28 | } 29 | 30 | impl AsyncWrite for RwWrapper { 31 | fn poll_write( 32 | self: Pin<&mut Self>, 33 | _cx: &mut Context<'_>, 34 | buf: &[u8], 35 | ) -> Poll> { 36 | Poll::Ready(Ok(buf.len())) 37 | } 38 | 39 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 40 | Poll::Ready(Ok(())) 41 | } 42 | 43 | fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 44 | Poll::Ready(Ok(())) 45 | } 46 | } 47 | 48 | fuzz_target!(|request: &[u8]| { 49 | let stream = RwWrapper::new(request.to_vec()); 50 | async_std::task::block_on(async_h1::accept("http://localhost", stream, |req| async { 51 | let mut res = http_types::Response::new(http_types::StatusCode::Ok); 52 | res.set_body(req); 53 | Ok(res) 54 | })) 55 | .ok(); 56 | }); 57 | -------------------------------------------------------------------------------- /tests/continue.rs: -------------------------------------------------------------------------------- 1 | mod test_utils; 2 | 3 | use async_std::{io, prelude::*, task}; 4 | use http_types::Result; 5 | use std::time::Duration; 6 | use test_utils::TestIO; 7 | 8 | const REQUEST_WITH_EXPECT: &[u8] = b"POST / HTTP/1.1\r\n\ 9 | Host: example.com\r\n\ 10 | Content-Length: 10\r\n\ 11 | Expect: 100-continue\r\n\r\n"; 12 | 13 | const SLEEP_DURATION: Duration = std::time::Duration::from_millis(100); 14 | #[async_std::test] 15 | async fn test_with_expect_when_reading_body() -> Result<()> { 16 | let (mut client, server) = TestIO::new(); 17 | client.write_all(REQUEST_WITH_EXPECT).await?; 18 | 19 | let (mut request, _) = async_h1::server::decode(server).await?.unwrap(); 20 | 21 | task::sleep(SLEEP_DURATION).await; //prove we're not just testing before we've written 22 | 23 | assert_eq!("", &client.read.to_string()); // we haven't written yet 24 | 25 | let join_handle = task::spawn(async move { 26 | let mut string = String::new(); 27 | request.read_to_string(&mut string).await?; //this triggers the 100-continue even though there's nothing to read yet 28 | io::Result::Ok(string) 29 | }); 30 | 31 | task::sleep(SLEEP_DURATION).await; // just long enough to wait for the channel and io 32 | 33 | assert_eq!("HTTP/1.1 100 Continue\r\n\r\n", &client.read.to_string()); 34 | 35 | client.write_all(b"0123456789").await?; 36 | 37 | assert_eq!("0123456789", &join_handle.await?); 38 | 39 | Ok(()) 40 | } 41 | 42 | #[async_std::test] 43 | async fn test_without_expect_when_not_reading_body() -> Result<()> { 44 | let (mut client, server) = TestIO::new(); 45 | client.write_all(REQUEST_WITH_EXPECT).await?; 46 | 47 | let (_, _) = async_h1::server::decode(server).await?.unwrap(); 48 | 49 | task::sleep(SLEEP_DURATION).await; // just long enough to wait for the channel 50 | 51 | assert_eq!("", &client.read.to_string()); // we haven't written 100-continue 52 | 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /src/read_notifier.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::pin::Pin; 3 | use std::task::{Context, Poll}; 4 | 5 | use async_channel::Sender; 6 | use futures_lite::io::{self, AsyncBufRead as BufRead, AsyncRead as Read}; 7 | 8 | /// ReadNotifier forwards [`async_std::io::Read`] and 9 | /// [`async_std::io::BufRead`] to an inner reader. When the 10 | /// ReadNotifier is read from (using `Read`, `ReadExt`, or `BufRead` 11 | /// methods), it sends a single message containing `()` on the 12 | /// channel. 13 | #[pin_project::pin_project] 14 | pub(crate) struct ReadNotifier { 15 | #[pin] 16 | reader: B, 17 | sender: Sender<()>, 18 | has_been_read: bool, 19 | } 20 | 21 | impl fmt::Debug for ReadNotifier { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | f.debug_struct("ReadNotifier") 24 | .field("read", &self.has_been_read) 25 | .finish() 26 | } 27 | } 28 | 29 | impl ReadNotifier { 30 | pub(crate) fn new(reader: B, sender: Sender<()>) -> Self { 31 | Self { 32 | reader, 33 | sender, 34 | has_been_read: false, 35 | } 36 | } 37 | } 38 | 39 | impl BufRead for ReadNotifier { 40 | fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 41 | self.project().reader.poll_fill_buf(cx) 42 | } 43 | 44 | fn consume(self: Pin<&mut Self>, amt: usize) { 45 | self.project().reader.consume(amt) 46 | } 47 | } 48 | 49 | impl Read for ReadNotifier { 50 | fn poll_read( 51 | self: Pin<&mut Self>, 52 | cx: &mut Context<'_>, 53 | buf: &mut [u8], 54 | ) -> Poll> { 55 | let this = self.project(); 56 | 57 | if !*this.has_been_read { 58 | if let Ok(()) = this.sender.try_send(()) { 59 | *this.has_been_read = true; 60 | }; 61 | } 62 | 63 | this.reader.poll_read(cx, buf) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/client_decode.rs: -------------------------------------------------------------------------------- 1 | mod test_utils; 2 | 3 | mod client_decode { 4 | use std::io::Write; 5 | 6 | use super::test_utils::CloseableCursor; 7 | use async_h1::client; 8 | use async_std::io::Cursor; 9 | use http_types::headers; 10 | use http_types::Response; 11 | use http_types::Result; 12 | use pretty_assertions::assert_eq; 13 | 14 | async fn decode_lines(s: Vec<&str>) -> Result { 15 | client::decode(Cursor::new(s.join("\r\n"))).await 16 | } 17 | 18 | #[async_std::test] 19 | async fn response_no_date() -> Result<()> { 20 | let res = decode_lines(vec![ 21 | "HTTP/1.1 200 OK", 22 | "transfer-encoding: chunked", 23 | "content-type: text/plain", 24 | "", 25 | "", 26 | ]) 27 | .await?; 28 | 29 | assert!(res.header(&headers::DATE).is_some()); 30 | Ok(()) 31 | } 32 | 33 | #[async_std::test] 34 | async fn multiple_header_values_for_same_header_name() -> Result<()> { 35 | let res = decode_lines(vec![ 36 | "HTTP/1.1 200 OK", 37 | "host: example.com", 38 | "content-length: 0", 39 | "set-cookie: sessionId=e8bb43229de9", 40 | "set-cookie: qwerty=219ffwef9w0f", 41 | "", 42 | "", 43 | ]) 44 | .await?; 45 | assert_eq!(res.header(&headers::SET_COOKIE).unwrap().iter().count(), 2); 46 | 47 | Ok(()) 48 | } 49 | 50 | #[async_std::test] 51 | async fn connection_closure() -> Result<()> { 52 | let mut cursor = CloseableCursor::default(); 53 | cursor.write_all(b"HTTP/1.1 200 OK\r\nhost: example.com")?; 54 | cursor.close(); 55 | assert_eq!( 56 | client::decode(cursor).await.unwrap_err().to_string(), 57 | "empty response" 58 | ); 59 | 60 | let cursor = CloseableCursor::default(); 61 | cursor.close(); 62 | assert_eq!( 63 | client::decode(cursor).await.unwrap_err().to_string(), 64 | "connection closed" 65 | ); 66 | 67 | Ok(()) 68 | } 69 | 70 | #[async_std::test] 71 | async fn response_newlines() -> Result<()> { 72 | let res = decode_lines(vec![ 73 | "HTTP/1.1 200 OK", 74 | "content-length: 78", 75 | "date: {DATE}", 76 | "content-type: text/plain; charset=utf-8", 77 | "", 78 | "http specifies headers are separated with \r\n but many servers don't do that", 79 | "", 80 | ]) 81 | .await?; 82 | 83 | assert_eq!( 84 | res[headers::CONTENT_LENGTH] 85 | .as_str() 86 | .parse::() 87 | .unwrap(), 88 | 78 89 | ); 90 | 91 | Ok(()) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

async-h1

2 |
3 | 4 | Asynchronous HTTP/1.1 parser. 5 | 6 |
7 | 8 |
9 | 10 |
11 | 12 | 13 | Crates.io version 15 | 16 | 17 | 18 | Download 20 | 21 | 22 | 23 | docs.rs docs 25 | 26 |
27 | 28 | 43 | 44 | ## Installation 45 | ```sh 46 | $ cargo add async-h1 47 | ``` 48 | 49 | ## Safety 50 | This crate uses ``#![forbid(unsafe_code)]`` to ensure everything is implemented in 51 | 100% Safe Rust. 52 | 53 | ## Minimum Supported Rust Version 54 | 55 | Given the rapidly-improving nature of async Rust, `async-h1` only 56 | guarantees it will work on the latest stable Rust compiler. Currently 57 | `async-h1` compiles on `rustc 1.40.0` and above, but we reserve the 58 | right to upgrade the minimum Rust version outside of major 59 | releases. If upgrading stable compiler versions is an issue we 60 | recommend pinning the version of `async-h1`. 61 | 62 | ## Contributing 63 | Want to join us? Check out our ["Contributing" guide][contributing] and take a 64 | look at some of these issues: 65 | 66 | - [Issues labeled "good first issue"][good-first-issue] 67 | - [Issues labeled "help wanted"][help-wanted] 68 | 69 | [contributing]: https://github.com/http-rs/async-h1/blob/main/.github/CONTRIBUTING.md 70 | [good-first-issue]: https://github.com/http-rs/async-h1/labels/good%20first%20issue 71 | [help-wanted]: https://github.com/http-rs/async-h1/labels/help%20wanted 72 | 73 | ## License 74 | 75 | 76 | Licensed under either of Apache License, Version 77 | 2.0 or MIT license at your option. 78 | 79 | 80 |
81 | 82 | 83 | Unless you explicitly state otherwise, any contribution intentionally submitted 84 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 85 | be dual licensed as above, without any additional terms or conditions. 86 | 87 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Contributions include code, documentation, answering user questions, running the 3 | project's infrastructure, and advocating for all types of users. 4 | 5 | The project welcomes all contributions from anyone willing to work in good faith 6 | with other contributors and the community. No contribution is too small and all 7 | contributions are valued. 8 | 9 | This guide explains the process for contributing to the project's GitHub 10 | Repository. 11 | 12 | - [Code of Conduct](#code-of-conduct) 13 | - [Bad Actors](#bad-actors) 14 | 15 | ## Code of Conduct 16 | The project has a [Code of Conduct](./CODE_OF_CONDUCT.md) that *all* 17 | contributors are expected to follow. This code describes the *minimum* behavior 18 | expectations for all contributors. 19 | 20 | As a contributor, how you choose to act and interact towards your 21 | fellow contributors, as well as to the community, will reflect back not only 22 | on yourself but on the project as a whole. The Code of Conduct is designed and 23 | intended, above all else, to help establish a culture within the project that 24 | allows anyone and everyone who wants to contribute to feel safe doing so. 25 | 26 | Should any individual act in any way that is considered in violation of the 27 | [Code of Conduct](./CODE_OF_CONDUCT.md), corrective actions will be taken. It is 28 | possible, however, for any individual to *act* in such a manner that is not in 29 | violation of the strict letter of the Code of Conduct guidelines while still 30 | going completely against the spirit of what that Code is intended to accomplish. 31 | 32 | Open, diverse, and inclusive communities live and die on the basis of trust. 33 | Contributors can disagree with one another so long as they trust that those 34 | disagreements are in good faith and everyone is working towards a common 35 | goal. 36 | 37 | ## Bad Actors 38 | All contributors to tacitly agree to abide by both the letter and 39 | spirit of the [Code of Conduct](./CODE_OF_CONDUCT.md). Failure, or 40 | unwillingness, to do so will result in contributions being respectfully 41 | declined. 42 | 43 | A *bad actor* is someone who repeatedly violates the *spirit* of the Code of 44 | Conduct through consistent failure to self-regulate the way in which they 45 | interact with other contributors in the project. In doing so, bad actors 46 | alienate other contributors, discourage collaboration, and generally reflect 47 | poorly on the project as a whole. 48 | 49 | Being a bad actor may be intentional or unintentional. Typically, unintentional 50 | bad behavior can be easily corrected by being quick to apologize and correct 51 | course *even if you are not entirely convinced you need to*. Giving other 52 | contributors the benefit of the doubt and having a sincere willingness to admit 53 | that you *might* be wrong is critical for any successful open collaboration. 54 | 55 | Don't be a bad actor. 56 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of 9 | experience, 10 | education, socio-economic status, nationality, personal appearance, race, 11 | religion, or sexual identity and orientation. 12 | 13 | ## Our Standards 14 | 15 | Examples of behavior that contributes to creating a positive environment 16 | include: 17 | 18 | - Using welcoming and inclusive language 19 | - Being respectful of differing viewpoints and experiences 20 | - Gracefully accepting constructive criticism 21 | - Focusing on what is best for the community 22 | - Showing empathy towards other community members 23 | 24 | Examples of unacceptable behavior by participants include: 25 | 26 | - The use of sexualized language or imagery and unwelcome sexual attention or 27 | advances 28 | - Trolling, insulting/derogatory comments, and personal or political attacks 29 | - Public or private harassment 30 | - Publishing others' private information, such as a physical or electronic 31 | address, without explicit permission 32 | - Other conduct which could reasonably be considered inappropriate in a 33 | professional setting 34 | 35 | 36 | ## Our Responsibilities 37 | 38 | Project maintainers are responsible for clarifying the standards of acceptable 39 | behavior and are expected to take appropriate and fair corrective action in 40 | response to any instances of unacceptable behavior. 41 | 42 | Project maintainers have the right and responsibility to remove, edit, or 43 | reject comments, commits, code, wiki edits, issues, and other contributions 44 | that are not aligned to this Code of Conduct, or to ban temporarily or 45 | permanently any contributor for other behaviors that they deem inappropriate, 46 | threatening, offensive, or harmful. 47 | 48 | ## Scope 49 | 50 | This Code of Conduct applies both within project spaces and in public spaces 51 | when an individual is representing the project or its community. Examples of 52 | representing a project or community include using an official project e-mail 53 | address, posting via an official social media account, or acting as an appointed 54 | representative at an online or offline event. Representation of a project may be 55 | further defined and clarified by project maintainers. 56 | 57 | ## Enforcement 58 | 59 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 60 | reported by contacting the project team at yoshuawuyts@gmail.com, or through 61 | IRC. All complaints will be reviewed and investigated and will result in a 62 | response that is deemed necessary and appropriate to the circumstances. The 63 | project team is obligated to maintain confidentiality with regard to the 64 | reporter of an incident. 65 | Further details of specific enforcement policies may be posted separately. 66 | 67 | Project maintainers who do not follow or enforce the Code of Conduct in good 68 | faith may face temporary or permanent repercussions as determined by other 69 | members of the project's leadership. 70 | 71 | ## Attribution 72 | 73 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 74 | available at 75 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 76 | -------------------------------------------------------------------------------- /src/server/encode.rs: -------------------------------------------------------------------------------- 1 | //! Process HTTP connections on the server. 2 | 3 | use std::io::Write; 4 | use std::pin::Pin; 5 | use std::task::{Context, Poll}; 6 | use std::time::SystemTime; 7 | 8 | use futures_lite::io::{self, AsyncRead as Read, Cursor}; 9 | use http_types::headers::{CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; 10 | use http_types::{Method, Response}; 11 | 12 | use crate::body_encoder::BodyEncoder; 13 | use crate::date::fmt_http_date; 14 | use crate::read_to_end; 15 | use crate::EncoderState; 16 | 17 | /// A streaming HTTP encoder. 18 | #[derive(Debug)] 19 | pub struct Encoder { 20 | response: Response, 21 | state: EncoderState, 22 | method: Method, 23 | } 24 | 25 | impl Read for Encoder { 26 | fn poll_read( 27 | mut self: Pin<&mut Self>, 28 | cx: &mut Context<'_>, 29 | buf: &mut [u8], 30 | ) -> Poll> { 31 | loop { 32 | self.state = match self.state { 33 | EncoderState::Start => EncoderState::Head(self.compute_head()?), 34 | 35 | EncoderState::Head(ref mut cursor) => { 36 | read_to_end!(Pin::new(cursor).poll_read(cx, buf)); 37 | 38 | if self.method == Method::Head { 39 | EncoderState::End 40 | } else { 41 | EncoderState::Body(BodyEncoder::new(self.response.take_body())) 42 | } 43 | } 44 | 45 | EncoderState::Body(ref mut encoder) => { 46 | read_to_end!(Pin::new(encoder).poll_read(cx, buf)); 47 | EncoderState::End 48 | } 49 | 50 | EncoderState::End => return Poll::Ready(Ok(0)), 51 | } 52 | } 53 | } 54 | } 55 | 56 | impl Encoder { 57 | /// Create a new instance of Encoder. 58 | pub fn new(response: Response, method: Method) -> Self { 59 | Self { 60 | method, 61 | response, 62 | state: EncoderState::Start, 63 | } 64 | } 65 | 66 | fn finalize_headers(&mut self) { 67 | // If the body isn't streaming, we can set the content-length ahead of time. Else we need to 68 | // send all items in chunks. 69 | if let Some(len) = self.response.len() { 70 | self.response.insert_header(CONTENT_LENGTH, len.to_string()); 71 | } else { 72 | self.response.insert_header(TRANSFER_ENCODING, "chunked"); 73 | } 74 | 75 | if self.response.header(DATE).is_none() { 76 | let date = fmt_http_date(SystemTime::now()); 77 | self.response.insert_header(DATE, date); 78 | } 79 | } 80 | 81 | /// Encode the headers to a buffer, the first time we poll. 82 | fn compute_head(&mut self) -> io::Result>> { 83 | let mut head = Vec::with_capacity(128); 84 | let reason = self.response.status().canonical_reason(); 85 | let status = self.response.status(); 86 | write!(head, "HTTP/1.1 {} {}\r\n", status, reason)?; 87 | 88 | self.finalize_headers(); 89 | let mut headers = self.response.iter().collect::>(); 90 | headers.sort_unstable_by_key(|(h, _)| h.as_str()); 91 | for (header, values) in headers { 92 | for value in values.iter() { 93 | write!(head, "{}: {}\r\n", header, value)?; 94 | } 95 | } 96 | write!(head, "\r\n")?; 97 | Ok(Cursor::new(head)) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/client/decode.rs: -------------------------------------------------------------------------------- 1 | use futures_lite::io::{AsyncRead as Read, BufReader}; 2 | use futures_lite::prelude::*; 3 | use http_types::{ensure, ensure_eq, format_err}; 4 | use http_types::{ 5 | headers::{CONTENT_LENGTH, DATE, TRANSFER_ENCODING}, 6 | Body, Response, StatusCode, 7 | }; 8 | 9 | use std::convert::TryFrom; 10 | 11 | use crate::chunked::ChunkedDecoder; 12 | use crate::date::fmt_http_date; 13 | use crate::{MAX_HEADERS, MAX_HEAD_LENGTH}; 14 | 15 | const CR: u8 = b'\r'; 16 | const LF: u8 = b'\n'; 17 | 18 | /// Decode an HTTP response on the client. 19 | pub async fn decode(reader: R) -> http_types::Result 20 | where 21 | R: Read + Unpin + Send + Sync + 'static, 22 | { 23 | let mut reader = BufReader::new(reader); 24 | let mut buf = Vec::new(); 25 | let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; 26 | let mut httparse_res = httparse::Response::new(&mut headers); 27 | 28 | // Keep reading bytes from the stream until we hit the end of the stream. 29 | loop { 30 | let bytes_read = reader.read_until(LF, &mut buf).await?; 31 | // No more bytes are yielded from the stream. 32 | 33 | match (bytes_read, buf.len()) { 34 | (0, 0) => return Err(format_err!("connection closed")), 35 | (0, _) => return Err(format_err!("empty response")), 36 | _ => {} 37 | } 38 | 39 | // Prevent CWE-400 DDOS with large HTTP Headers. 40 | ensure!( 41 | buf.len() < MAX_HEAD_LENGTH, 42 | "Head byte length should be less than 8kb" 43 | ); 44 | 45 | // We've hit the end delimiter of the stream. 46 | let idx = buf.len() - 1; 47 | if idx >= 3 && buf[idx - 3..=idx] == [CR, LF, CR, LF] { 48 | break; 49 | } 50 | if idx >= 1 && buf[idx - 1..=idx] == [LF, LF] { 51 | break; 52 | } 53 | } 54 | 55 | // Convert our header buf into an httparse instance, and validate. 56 | let status = httparse_res.parse(&buf)?; 57 | ensure!(!status.is_partial(), "Malformed HTTP head"); 58 | 59 | let code = httparse_res.code; 60 | let code = code.ok_or_else(|| format_err!("No status code found"))?; 61 | 62 | // Convert httparse headers + body into a `http_types::Response` type. 63 | let version = httparse_res.version; 64 | let version = version.ok_or_else(|| format_err!("No version found"))?; 65 | ensure_eq!(version, 1, "Unsupported HTTP version"); 66 | 67 | let mut res = Response::new(StatusCode::try_from(code)?); 68 | for header in httparse_res.headers.iter() { 69 | res.append_header(header.name, std::str::from_utf8(header.value)?); 70 | } 71 | 72 | if res.header(DATE).is_none() { 73 | let date = fmt_http_date(std::time::SystemTime::now()); 74 | res.insert_header(DATE, &format!("date: {}\r\n", date)[..]); 75 | } 76 | 77 | let content_length = res.header(CONTENT_LENGTH); 78 | let transfer_encoding = res.header(TRANSFER_ENCODING); 79 | 80 | ensure!( 81 | content_length.is_none() || transfer_encoding.is_none(), 82 | "Unexpected Content-Length header" 83 | ); 84 | 85 | if let Some(encoding) = transfer_encoding { 86 | if encoding.last().as_str() == "chunked" { 87 | let trailers_sender = res.send_trailers(); 88 | let reader = BufReader::new(ChunkedDecoder::new(reader, trailers_sender)); 89 | res.set_body(Body::from_reader(reader, None)); 90 | 91 | // Return the response. 92 | return Ok(res); 93 | } 94 | } 95 | 96 | // Check for Content-Length. 97 | if let Some(len) = content_length { 98 | let len = len.last().as_str().parse::()?; 99 | res.set_body(Body::from_reader(reader.take(len as u64), Some(len))); 100 | } 101 | 102 | // Return the response. 103 | Ok(res) 104 | } 105 | -------------------------------------------------------------------------------- /tests/server_decode.rs: -------------------------------------------------------------------------------- 1 | mod test_utils; 2 | mod server_decode { 3 | use super::test_utils::TestIO; 4 | use async_std::io::prelude::*; 5 | use http_types::headers::TRANSFER_ENCODING; 6 | use http_types::Request; 7 | use http_types::Result; 8 | use http_types::Url; 9 | use pretty_assertions::assert_eq; 10 | 11 | async fn decode_lines(lines: Vec<&str>) -> Result> { 12 | let s = lines.join("\r\n"); 13 | let (mut client, server) = TestIO::new(); 14 | client.write_all(s.as_bytes()).await?; 15 | client.close(); 16 | async_h1::server::decode(server) 17 | .await 18 | .map(|r| r.map(|(r, _)| r)) 19 | } 20 | 21 | #[async_std::test] 22 | async fn post_with_body() -> Result<()> { 23 | let mut request = decode_lines(vec![ 24 | "POST / HTTP/1.1", 25 | "host: localhost:8080", 26 | "content-length: 5", 27 | "content-type: text/plain;charset=utf-8", 28 | "another-header: header value", 29 | "another-header: other header value", 30 | "", 31 | "hello", 32 | "", 33 | ]) 34 | .await? 35 | .unwrap(); 36 | 37 | assert_eq!(request.method(), http_types::Method::Post); 38 | assert_eq!(request.body_string().await?, "hello"); 39 | assert_eq!(request.content_type(), Some(http_types::mime::PLAIN)); 40 | assert_eq!(request.version(), Some(http_types::Version::Http1_1)); 41 | assert_eq!(request.host(), Some("localhost:8080")); 42 | assert_eq!( 43 | request.url(), 44 | &Url::parse("http://localhost:8080/").unwrap() 45 | ); 46 | 47 | let custom_header = request.header("another-header").unwrap(); 48 | assert_eq!(custom_header[0], "header value"); 49 | assert_eq!(custom_header[1], "other header value"); 50 | 51 | Ok(()) 52 | } 53 | 54 | #[async_std::test] 55 | async fn chunked() -> Result<()> { 56 | let mut request = decode_lines(vec![ 57 | "POST / HTTP/1.1", 58 | "host: localhost:8080", 59 | "transfer-encoding: chunked", 60 | "content-type: text/plain;charset=utf-8", 61 | "", 62 | "1", 63 | "h", 64 | "1", 65 | "e", 66 | "3", 67 | "llo", 68 | "0", 69 | "", 70 | ]) 71 | .await? 72 | .unwrap(); 73 | 74 | assert_eq!(request[TRANSFER_ENCODING], "chunked"); 75 | assert_eq!(request.body_string().await?, "hello"); 76 | 77 | Ok(()) 78 | } 79 | 80 | #[ignore = r#" 81 | the test previously did not actually assert the correct thing prevously 82 | and the behavior does not yet work as intended 83 | "#] 84 | #[async_std::test] 85 | async fn invalid_trailer() -> Result<()> { 86 | let mut request = decode_lines(vec![ 87 | "GET / HTTP/1.1", 88 | "host: domain.com", 89 | "content-type: application/octet-stream", 90 | "transfer-encoding: chunked", 91 | "trailer: x-invalid", 92 | "", 93 | "0", 94 | "x-invalid: å", 95 | "", 96 | ]) 97 | .await? 98 | .unwrap(); 99 | 100 | assert!(request.body_string().await.is_err()); 101 | 102 | Ok(()) 103 | } 104 | 105 | #[async_std::test] 106 | async fn unexpected_eof() -> Result<()> { 107 | let mut request = decode_lines(vec![ 108 | "POST / HTTP/1.1", 109 | "host: example.com", 110 | "content-type: text/plain", 111 | "content-length: 11", 112 | "", 113 | "not 11", 114 | ]) 115 | .await? 116 | .unwrap(); 117 | 118 | let mut string = String::new(); 119 | // we use read_to_string because although not currently the 120 | // case, at some point soon body_string will error if the 121 | // retrieved content length is not the same as the header (if 122 | // the client disconnects) 123 | request.read_to_string(&mut string).await?; 124 | assert_eq!(string, "not 11"); 125 | 126 | Ok(()) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/chunked/encoder.rs: -------------------------------------------------------------------------------- 1 | use std::pin::Pin; 2 | use std::task::{Context, Poll}; 3 | 4 | use futures_lite::io::AsyncRead as Read; 5 | use futures_lite::{io, ready}; 6 | 7 | /// An encoder for chunked encoding. 8 | #[derive(Debug)] 9 | pub(crate) struct ChunkedEncoder { 10 | reader: R, 11 | done: bool, 12 | } 13 | 14 | impl ChunkedEncoder { 15 | /// Create a new instance. 16 | pub(crate) fn new(reader: R) -> Self { 17 | Self { 18 | reader, 19 | done: false, 20 | } 21 | } 22 | } 23 | 24 | impl Read for ChunkedEncoder { 25 | fn poll_read( 26 | mut self: Pin<&mut Self>, 27 | cx: &mut Context<'_>, 28 | buf: &mut [u8], 29 | ) -> Poll> { 30 | if self.done { 31 | return Poll::Ready(Ok(0)); 32 | } 33 | let reader = &mut self.reader; 34 | 35 | let max_bytes_to_read = max_bytes_to_read(buf.len()); 36 | 37 | let bytes = ready!(Pin::new(reader).poll_read(cx, &mut buf[..max_bytes_to_read]))?; 38 | if bytes == 0 { 39 | self.done = true; 40 | } 41 | let start = format!("{:X}\r\n", bytes); 42 | let start_length = start.as_bytes().len(); 43 | let total = bytes + start_length + 2; 44 | buf.copy_within(..bytes, start_length); 45 | buf[..start_length].copy_from_slice(start.as_bytes()); 46 | buf[total - 2..total].copy_from_slice(b"\r\n"); 47 | Poll::Ready(Ok(total)) 48 | } 49 | } 50 | 51 | fn max_bytes_to_read(buf_len: usize) -> usize { 52 | if buf_len < 6 { 53 | // the minimum read size is of 6 represents one byte of 54 | // content from the body. the other five bytes are 1\r\n_\r\n 55 | // where _ is the actual content in question 56 | panic!("buffers of length {} are too small for this implementation. if this is a problem for you, please open an issue", buf_len); 57 | } 58 | 59 | let bytes_remaining_after_two_cr_lns = (buf_len - 4) as f64; 60 | 61 | // the maximum number of bytes that the hex representation of remaining bytes might take 62 | let max_bytes_of_hex_framing = bytes_remaining_after_two_cr_lns.log2() / 4f64; 63 | 64 | (bytes_remaining_after_two_cr_lns - max_bytes_of_hex_framing.ceil()) as usize 65 | } 66 | 67 | #[cfg(test)] 68 | mod test_bytes_to_read { 69 | #[test] 70 | fn simple_check_of_known_values() { 71 | // the marked rows are the most important part of this test, 72 | // and a nonobvious but intentional consequence of the 73 | // implementation. in order to avoid overflowing, we must use 74 | // one fewer than the available buffer bytes because 75 | // increasing the read size increase the number of framed 76 | // bytes by two. This occurs when the hex representation of 77 | // the content bytes is near an increase in order of magnitude 78 | // (F->10, FF->100, FFF-> 1000, etc) 79 | let values = vec![ 80 | (6, 1), // 1 81 | (7, 2), // 2 82 | (20, 15), // F 83 | (21, 15), // F <- 84 | (22, 16), // 10 85 | (23, 17), // 11 86 | (260, 254), // FE 87 | (261, 254), // FE <- 88 | (262, 255), // FF <- 89 | (263, 256), // 100 90 | (4100, 4093), // FFD 91 | (4101, 4093), // FFD <- 92 | (4102, 4094), // FFE <- 93 | (4103, 4095), // FFF <- 94 | (4104, 4096), // 1000 95 | ]; 96 | 97 | for (input, expected) in values { 98 | let actual = super::max_bytes_to_read(input); 99 | assert_eq!( 100 | actual, expected, 101 | "\n\nexpected max_bytes_to_read({}) to be {}, but it was {}", 102 | input, expected, actual 103 | ); 104 | 105 | // testing the test: 106 | let used_bytes = expected + 4 + format!("{:X}", expected).len(); 107 | assert!( 108 | used_bytes == input || used_bytes == input - 1, 109 | "\n\nfor an input of {}, expected used bytes to be {} or {}, but was {}", 110 | input, 111 | input, 112 | input - 1, 113 | used_bytes 114 | ); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/client/encode.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::pin::Pin; 3 | use std::task::{Context, Poll}; 4 | 5 | use futures_lite::io::{self, AsyncRead as Read, Cursor}; 6 | use http_types::headers::{CONTENT_LENGTH, HOST, TRANSFER_ENCODING}; 7 | use http_types::{Method, Request}; 8 | 9 | use crate::body_encoder::BodyEncoder; 10 | use crate::read_to_end; 11 | use crate::EncoderState; 12 | 13 | /// An HTTP encoder. 14 | #[doc(hidden)] 15 | #[derive(Debug)] 16 | pub struct Encoder { 17 | request: Request, 18 | state: EncoderState, 19 | } 20 | 21 | impl Encoder { 22 | /// build a new client encoder 23 | pub fn new(request: Request) -> Self { 24 | Self { 25 | request, 26 | state: EncoderState::Start, 27 | } 28 | } 29 | 30 | fn finalize_headers(&mut self) -> io::Result<()> { 31 | if self.request.header(HOST).is_none() { 32 | let url = self.request.url(); 33 | let host = url 34 | .host_str() 35 | .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Missing hostname"))? 36 | .to_owned(); 37 | 38 | if let Some(port) = url.port() { 39 | self.request 40 | .insert_header(HOST, format!("{}:{}", host, port)); 41 | } else { 42 | self.request.insert_header(HOST, host); 43 | }; 44 | } 45 | 46 | // Insert Proxy-Connection header when method is CONNECT 47 | if self.request.method() == Method::Connect { 48 | self.request.insert_header("proxy-connection", "keep-alive"); 49 | } 50 | 51 | // If the body isn't streaming, we can set the content-length ahead of time. Else we need to 52 | // send all items in chunks. 53 | if let Some(len) = self.request.len() { 54 | self.request.insert_header(CONTENT_LENGTH, len.to_string()); 55 | } else { 56 | self.request.insert_header(TRANSFER_ENCODING, "chunked"); 57 | } 58 | 59 | Ok(()) 60 | } 61 | 62 | fn compute_head(&mut self) -> io::Result>> { 63 | let mut buf = Vec::with_capacity(128); 64 | let url = self.request.url(); 65 | let method = self.request.method(); 66 | write!(buf, "{} ", method)?; 67 | 68 | // A client sending a CONNECT request MUST consists of only the host 69 | // name and port number of the tunnel destination, separated by a colon. 70 | // See: https://tools.ietf.org/html/rfc7231#section-4.3.6 71 | if method == Method::Connect { 72 | let host = url 73 | .host_str() 74 | .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Missing hostname"))?; 75 | 76 | let port = url.port_or_known_default().ok_or_else(|| { 77 | io::Error::new( 78 | io::ErrorKind::InvalidData, 79 | "Unexpected scheme with no default port", 80 | ) 81 | })?; 82 | 83 | write!(buf, "{}:{}", host, port)?; 84 | } else { 85 | write!(buf, "{}", url.path())?; 86 | if let Some(query) = url.query() { 87 | write!(buf, "?{}", query)?; 88 | } 89 | } 90 | 91 | write!(buf, " HTTP/1.1\r\n")?; 92 | 93 | self.finalize_headers()?; 94 | let mut headers = self.request.iter().collect::>(); 95 | headers.sort_unstable_by_key(|(h, _)| if **h == HOST { "0" } else { h.as_str() }); 96 | for (header, values) in headers { 97 | for value in values.iter() { 98 | write!(buf, "{}: {}\r\n", header, value)?; 99 | } 100 | } 101 | 102 | write!(buf, "\r\n")?; 103 | Ok(Cursor::new(buf)) 104 | } 105 | } 106 | 107 | impl Read for Encoder { 108 | fn poll_read( 109 | mut self: Pin<&mut Self>, 110 | cx: &mut Context<'_>, 111 | buf: &mut [u8], 112 | ) -> Poll> { 113 | loop { 114 | self.state = match self.state { 115 | EncoderState::Start => EncoderState::Head(self.compute_head()?), 116 | 117 | EncoderState::Head(ref mut cursor) => { 118 | read_to_end!(Pin::new(cursor).poll_read(cx, buf)); 119 | EncoderState::Body(BodyEncoder::new(self.request.take_body())) 120 | } 121 | 122 | EncoderState::Body(ref mut encoder) => { 123 | read_to_end!(Pin::new(encoder).poll_read(cx, buf)); 124 | EncoderState::End 125 | } 126 | 127 | EncoderState::End => return Poll::Ready(Ok(0)), 128 | } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Streaming async HTTP 1.1 parser. 2 | //! 3 | //! At its core HTTP is a stateful RPC protocol, where a client and server 4 | //! communicate with one another by encoding and decoding messages between them. 5 | //! 6 | //! - `client` encodes HTTP requests, and decodes HTTP responses. 7 | //! - `server` decodes HTTP requests, and encodes HTTP responses. 8 | //! 9 | //! A client always starts the HTTP connection. The lifetime of an HTTP 10 | //! connection looks like this: 11 | //! 12 | //! ```txt 13 | //! 1. encode 2. decode 14 | //! \ / 15 | //! -> request -> 16 | //! client server 17 | //! <- response <- 18 | //! / \ 19 | //! 4. decode 3. encode 20 | //! ``` 21 | //! 22 | //! See also [`async-tls`](https://docs.rs/async-tls), 23 | //! [`async-std`](https://docs.rs/async-std). 24 | //! 25 | //! # Example 26 | //! 27 | //! __HTTP client__ 28 | //! 29 | //! ```no_run 30 | //! use async_std::net::TcpStream; 31 | //! use http_types::{Method, Request, Url}; 32 | //! 33 | //! #[async_std::main] 34 | //! async fn main() -> http_types::Result<()> { 35 | //! let stream = TcpStream::connect("127.0.0.1:8080").await?; 36 | //! 37 | //! let peer_addr = stream.peer_addr()?; 38 | //! println!("connecting to {}", peer_addr); 39 | //! let url = Url::parse(&format!("http://{}/foo", peer_addr))?; 40 | //! 41 | //! let req = Request::new(Method::Get, url); 42 | //! let res = async_h1::connect(stream.clone(), req).await?; 43 | //! println!("{:?}", res); 44 | //! 45 | //! Ok(()) 46 | //! } 47 | //! ``` 48 | //! 49 | //! __HTTP Server__ 50 | //! 51 | //! ```no_run 52 | //! use async_std::net::{TcpStream, TcpListener}; 53 | //! use async_std::prelude::*; 54 | //! use async_std::task; 55 | //! use http_types::{Response, StatusCode}; 56 | //! 57 | //! #[async_std::main] 58 | //! async fn main() -> http_types::Result<()> { 59 | //! // Open up a TCP connection and create a URL. 60 | //! let listener = TcpListener::bind(("127.0.0.1", 8080)).await?; 61 | //! let addr = format!("http://{}", listener.local_addr()?); 62 | //! println!("listening on {}", addr); 63 | //! 64 | //! // For each incoming TCP connection, spawn a task and call `accept`. 65 | //! let mut incoming = listener.incoming(); 66 | //! while let Some(stream) = incoming.next().await { 67 | //! let stream = stream?; 68 | //! task::spawn(async { 69 | //! if let Err(err) = accept(stream).await { 70 | //! eprintln!("{}", err); 71 | //! } 72 | //! }); 73 | //! } 74 | //! Ok(()) 75 | //! } 76 | //! 77 | //! // Take a TCP stream, and convert it into sequential HTTP request / response pairs. 78 | //! async fn accept(stream: TcpStream) -> http_types::Result<()> { 79 | //! println!("starting new connection from {}", stream.peer_addr()?); 80 | //! async_h1::accept(stream.clone(), |_req| async move { 81 | //! let mut res = Response::new(StatusCode::Ok); 82 | //! res.insert_header("Content-Type", "text/plain"); 83 | //! res.set_body("Hello"); 84 | //! Ok(res) 85 | //! }) 86 | //! .await?; 87 | //! Ok(()) 88 | //! } 89 | //! ``` 90 | 91 | #![forbid(unsafe_code)] 92 | #![deny(missing_debug_implementations, nonstandard_style, rust_2018_idioms)] 93 | #![warn(missing_docs, missing_doc_code_examples, unreachable_pub)] 94 | #![cfg_attr(test, deny(warnings))] 95 | #![allow(clippy::if_same_then_else)] 96 | #![allow(clippy::len_zero)] 97 | #![allow(clippy::match_bool)] 98 | #![allow(clippy::unreadable_literal)] 99 | 100 | /// The maximum amount of headers parsed on the server. 101 | const MAX_HEADERS: usize = 128; 102 | 103 | /// The maximum length of the head section we'll try to parse. 104 | /// See: https://nodejs.org/en/blog/vulnerability/november-2018-security-releases/#denial-of-service-with-large-http-headers-cve-2018-12121 105 | const MAX_HEAD_LENGTH: usize = 8 * 1024; 106 | 107 | mod body_encoder; 108 | mod chunked; 109 | mod date; 110 | mod read_notifier; 111 | 112 | pub mod client; 113 | pub mod server; 114 | 115 | use body_encoder::BodyEncoder; 116 | pub use client::connect; 117 | use futures_lite::io::Cursor; 118 | pub use server::{accept, accept_with_opts, ServerOptions}; 119 | 120 | #[derive(Debug)] 121 | pub(crate) enum EncoderState { 122 | Start, 123 | Head(Cursor>), 124 | Body(BodyEncoder), 125 | End, 126 | } 127 | 128 | /// like ready! but early-returns the Poll> early in all situations other than Ready(Ok(0)) 129 | #[macro_export] 130 | macro_rules! read_to_end { 131 | ($expr:expr) => { 132 | match $expr { 133 | Poll::Ready(Ok(0)) => (), 134 | other => return other, 135 | } 136 | }; 137 | } 138 | -------------------------------------------------------------------------------- /tests/server_encode.rs: -------------------------------------------------------------------------------- 1 | mod server_encode { 2 | use async_h1::server::Encoder; 3 | use async_std::io::Cursor; 4 | use async_std::io::ReadExt; 5 | use http_types::Body; 6 | use http_types::Result; 7 | use http_types::StatusCode; 8 | use http_types::{Method, Response}; 9 | use pretty_assertions::assert_eq; 10 | 11 | async fn encode_to_string( 12 | response: Response, 13 | len: usize, 14 | method: Method, 15 | ) -> http_types::Result { 16 | let mut buf = vec![]; 17 | let mut encoder = Encoder::new(response, method); 18 | loop { 19 | let mut inner_buf = vec![0; len]; 20 | let bytes = encoder.read(&mut inner_buf).await?; 21 | buf.extend_from_slice(&inner_buf[..bytes]); 22 | if bytes == 0 { 23 | return Ok(String::from_utf8(buf)?); 24 | } 25 | } 26 | } 27 | 28 | async fn assert_encoded(len: usize, method: Method, response: Response, lines: Vec<&str>) { 29 | assert_eq!( 30 | encode_to_string(response, len, method) 31 | .await 32 | .unwrap() 33 | .split("\r\n") 34 | .map(|line| { 35 | if line.starts_with("date:") { 36 | "date: {DATE}" 37 | } else { 38 | line 39 | } 40 | }) 41 | .collect::>() 42 | .join("\r\n"), 43 | lines.join("\r\n") 44 | ); 45 | } 46 | 47 | #[async_std::test] 48 | async fn basic() -> Result<()> { 49 | let res = Response::new(StatusCode::Ok); 50 | 51 | assert_encoded( 52 | 100, 53 | Method::Get, 54 | res, 55 | vec![ 56 | "HTTP/1.1 200 OK", 57 | "content-length: 0", 58 | "date: {DATE}", 59 | "", 60 | "", 61 | ], 62 | ) 63 | .await; 64 | 65 | Ok(()) 66 | } 67 | 68 | #[async_std::test] 69 | async fn basic_404() -> Result<()> { 70 | let res = Response::new(StatusCode::NotFound); 71 | 72 | assert_encoded( 73 | 100, 74 | Method::Get, 75 | res, 76 | vec![ 77 | "HTTP/1.1 404 Not Found", 78 | "content-length: 0", 79 | "date: {DATE}", 80 | "", 81 | "", 82 | ], 83 | ) 84 | .await; 85 | 86 | Ok(()) 87 | } 88 | 89 | #[async_std::test] 90 | async fn chunked() -> Result<()> { 91 | let mut res = Response::new(StatusCode::Ok); 92 | res.set_body(Body::from_reader(Cursor::new("hello world"), None)); 93 | 94 | assert_encoded( 95 | 10, 96 | Method::Get, 97 | res, 98 | vec![ 99 | "HTTP/1.1 200 OK", 100 | "content-type: application/octet-stream", 101 | "date: {DATE}", 102 | "transfer-encoding: chunked", 103 | "", 104 | "5", 105 | "hello", 106 | "5", 107 | " worl", 108 | "1", 109 | "d", 110 | "0", 111 | "", 112 | "", 113 | ], 114 | ) 115 | .await; 116 | Ok(()) 117 | } 118 | 119 | #[async_std::test] 120 | async fn head_request_fixed_body() -> Result<()> { 121 | let mut res = Response::new(StatusCode::Ok); 122 | res.set_body("empty body because head request"); 123 | 124 | assert_encoded( 125 | 10, 126 | Method::Head, 127 | res, 128 | vec![ 129 | "HTTP/1.1 200 OK", 130 | "content-length: 31", 131 | "content-type: text/plain;charset=utf-8", 132 | "date: {DATE}", 133 | "", 134 | "", 135 | ], 136 | ) 137 | .await; 138 | 139 | Ok(()) 140 | } 141 | 142 | #[async_std::test] 143 | async fn head_request_chunked_body() -> Result<()> { 144 | let mut res = Response::new(StatusCode::Ok); 145 | res.set_body(Body::from_reader( 146 | Cursor::new("empty body because head request"), 147 | None, 148 | )); 149 | 150 | assert_encoded( 151 | 10, 152 | Method::Head, 153 | res, 154 | vec![ 155 | "HTTP/1.1 200 OK", 156 | "content-type: application/octet-stream", 157 | "date: {DATE}", 158 | "transfer-encoding: chunked", 159 | "", 160 | "", 161 | ], 162 | ) 163 | .await; 164 | 165 | Ok(()) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /tests/client_encode.rs: -------------------------------------------------------------------------------- 1 | mod client_encode { 2 | use async_h1::client; 3 | use async_std::io::Cursor; 4 | use async_std::prelude::*; 5 | use client::Encoder; 6 | use http_types::Body; 7 | use http_types::Result; 8 | use http_types::{Method, Request, Url}; 9 | use pretty_assertions::assert_eq; 10 | 11 | async fn encode_to_string(request: Request, len: usize) -> http_types::Result { 12 | let mut buf = vec![]; 13 | let mut encoder = Encoder::new(request); 14 | loop { 15 | let mut inner_buf = vec![0; len]; 16 | let bytes = encoder.read(&mut inner_buf).await?; 17 | buf.extend_from_slice(&inner_buf[..bytes]); 18 | if bytes == 0 { 19 | return Ok(String::from_utf8(buf)?); 20 | } 21 | } 22 | } 23 | 24 | #[async_std::test] 25 | async fn client_encode_request_add_date() -> Result<()> { 26 | let url = Url::parse("http://localhost:8080").unwrap(); 27 | let mut req = Request::new(Method::Post, url); 28 | req.set_body("hello"); 29 | 30 | assert_encoded( 31 | 100, 32 | req, 33 | vec![ 34 | "POST / HTTP/1.1", 35 | "host: localhost:8080", 36 | "content-length: 5", 37 | "content-type: text/plain;charset=utf-8", 38 | "", 39 | "hello", 40 | ], 41 | ) 42 | .await; 43 | Ok(()) 44 | } 45 | 46 | #[async_std::test] 47 | async fn client_encode_request_with_connect() -> Result<()> { 48 | let url = Url::parse("https://example.com:443").unwrap(); 49 | let req = Request::new(Method::Connect, url); 50 | 51 | assert_encoded( 52 | 100, 53 | req, 54 | vec![ 55 | "CONNECT example.com:443 HTTP/1.1", 56 | "host: example.com", 57 | "content-length: 0", 58 | "proxy-connection: keep-alive", 59 | "", 60 | "", 61 | ], 62 | ) 63 | .await; 64 | 65 | Ok(()) 66 | } 67 | 68 | // The fragment of an URL is not send to the server, see RFC7230 and RFC3986. 69 | #[async_std::test] 70 | async fn client_encode_request_with_fragment() -> Result<()> { 71 | let url = Url::parse("http://example.com/path?query#fragment").unwrap(); 72 | let req = Request::new(Method::Get, url); 73 | 74 | assert_encoded( 75 | 10, 76 | req, 77 | vec![ 78 | "GET /path?query HTTP/1.1", 79 | "host: example.com", 80 | "content-length: 0", 81 | "", 82 | "", 83 | ], 84 | ) 85 | .await; 86 | 87 | Ok(()) 88 | } 89 | 90 | async fn assert_encoded(len: usize, req: Request, lines: Vec<&str>) { 91 | assert_eq!( 92 | encode_to_string(req, len).await.unwrap(), 93 | lines.join("\r\n"), 94 | ) 95 | } 96 | 97 | #[async_std::test] 98 | async fn client_encode_chunked_body() -> Result<()> { 99 | let url = Url::parse("http://example.com/path?query").unwrap(); 100 | let mut req = Request::new(Method::Get, url.clone()); 101 | req.set_body(Body::from_reader(Cursor::new("hello world"), None)); 102 | 103 | assert_encoded( 104 | 10, 105 | req, 106 | vec![ 107 | "GET /path?query HTTP/1.1", 108 | "host: example.com", 109 | "content-type: application/octet-stream", 110 | "transfer-encoding: chunked", 111 | "", 112 | "5", 113 | "hello", 114 | "5", 115 | " worl", 116 | "1", 117 | "d", 118 | "0", 119 | "", 120 | "", 121 | ], 122 | ) 123 | .await; 124 | 125 | let mut req = Request::new(Method::Get, url.clone()); 126 | req.set_body(Body::from_reader(Cursor::new("hello world"), None)); 127 | 128 | assert_encoded( 129 | 16, 130 | req, 131 | vec![ 132 | "GET /path?query HTTP/1.1", 133 | "host: example.com", 134 | "content-type: application/octet-stream", 135 | "transfer-encoding: chunked", 136 | "", 137 | "B", 138 | "hello world", 139 | "0", 140 | "", 141 | "", 142 | ], 143 | ) 144 | .await; 145 | 146 | let mut req = Request::new(Method::Get, url.clone()); 147 | req.set_body(Body::from_reader( 148 | Cursor::new( 149 | "this response is more than 32 bytes long in order to require a second hex digit", 150 | ), 151 | None, 152 | )); 153 | 154 | assert_encoded( 155 | 32, 156 | req, 157 | vec![ 158 | "GET /path?query HTTP/1.1", 159 | "host: example.com", 160 | "content-type: application/octet-stream", 161 | "transfer-encoding: chunked", 162 | "", 163 | "1A", 164 | "this response is more than", 165 | "1A", 166 | " 32 bytes long in order to", 167 | "1A", 168 | " require a second hex digi", 169 | "1", 170 | "t", 171 | "0", 172 | "", 173 | "", 174 | ], 175 | ) 176 | .await; 177 | 178 | Ok(()) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/server/mod.rs: -------------------------------------------------------------------------------- 1 | //! Process HTTP connections on the server. 2 | 3 | use async_io::Timer; 4 | use futures_lite::io::{self, AsyncRead as Read, AsyncWrite as Write}; 5 | use futures_lite::prelude::*; 6 | use http_types::headers::{CONNECTION, UPGRADE}; 7 | use http_types::upgrade::Connection; 8 | use http_types::{Request, Response, StatusCode}; 9 | use std::{future::Future, marker::PhantomData, time::Duration}; 10 | mod body_reader; 11 | mod decode; 12 | mod encode; 13 | 14 | pub use decode::decode; 15 | pub use encode::Encoder; 16 | 17 | /// Configure the server. 18 | #[derive(Debug, Clone)] 19 | pub struct ServerOptions { 20 | /// Timeout to handle headers. Defaults to 60s. 21 | headers_timeout: Option, 22 | } 23 | 24 | impl Default for ServerOptions { 25 | fn default() -> Self { 26 | Self { 27 | headers_timeout: Some(Duration::from_secs(60)), 28 | } 29 | } 30 | } 31 | 32 | /// Accept a new incoming HTTP/1.1 connection. 33 | /// 34 | /// Supports `KeepAlive` requests by default. 35 | pub async fn accept(io: RW, endpoint: F) -> http_types::Result<()> 36 | where 37 | RW: Read + Write + Clone + Send + Sync + Unpin + 'static, 38 | F: Fn(Request) -> Fut, 39 | Fut: Future>, 40 | { 41 | Server::new(io, endpoint).accept().await 42 | } 43 | 44 | /// Accept a new incoming HTTP/1.1 connection. 45 | /// 46 | /// Supports `KeepAlive` requests by default. 47 | pub async fn accept_with_opts( 48 | io: RW, 49 | endpoint: F, 50 | opts: ServerOptions, 51 | ) -> http_types::Result<()> 52 | where 53 | RW: Read + Write + Clone + Send + Sync + Unpin + 'static, 54 | F: Fn(Request) -> Fut, 55 | Fut: Future>, 56 | { 57 | Server::new(io, endpoint).with_opts(opts).accept().await 58 | } 59 | 60 | /// struct for server 61 | #[derive(Debug)] 62 | pub struct Server { 63 | io: RW, 64 | endpoint: F, 65 | opts: ServerOptions, 66 | _phantom: PhantomData, 67 | } 68 | 69 | /// An enum that represents whether the server should accept a subsequent request 70 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 71 | pub enum ConnectionStatus { 72 | /// The server should not accept another request 73 | Close, 74 | 75 | /// The server may accept another request 76 | KeepAlive, 77 | } 78 | 79 | impl Server 80 | where 81 | RW: Read + Write + Clone + Send + Sync + Unpin + 'static, 82 | F: Fn(Request) -> Fut, 83 | Fut: Future>, 84 | { 85 | /// builds a new server 86 | pub fn new(io: RW, endpoint: F) -> Self { 87 | Self { 88 | io, 89 | endpoint, 90 | opts: Default::default(), 91 | _phantom: PhantomData, 92 | } 93 | } 94 | 95 | /// with opts 96 | pub fn with_opts(mut self, opts: ServerOptions) -> Self { 97 | self.opts = opts; 98 | self 99 | } 100 | 101 | /// accept in a loop 102 | pub async fn accept(&mut self) -> http_types::Result<()> { 103 | while ConnectionStatus::KeepAlive == self.accept_one().await? {} 104 | Ok(()) 105 | } 106 | 107 | /// accept one request 108 | pub async fn accept_one(&mut self) -> http_types::Result 109 | where 110 | RW: Read + Write + Clone + Send + Sync + Unpin + 'static, 111 | F: Fn(Request) -> Fut, 112 | Fut: Future>, 113 | { 114 | // Decode a new request, timing out if this takes longer than the timeout duration. 115 | let fut = decode(self.io.clone()); 116 | 117 | let (req, mut body) = if let Some(timeout_duration) = self.opts.headers_timeout { 118 | match fut 119 | .or(async { 120 | Timer::after(timeout_duration).await; 121 | Ok(None) 122 | }) 123 | .await 124 | { 125 | Ok(Some(r)) => r, 126 | Ok(None) => return Ok(ConnectionStatus::Close), /* EOF or timeout */ 127 | Err(e) => return Err(e), 128 | } 129 | } else { 130 | match fut.await? { 131 | Some(r) => r, 132 | None => return Ok(ConnectionStatus::Close), /* EOF */ 133 | } 134 | }; 135 | 136 | let has_upgrade_header = req.header(UPGRADE).is_some(); 137 | let connection_header_as_str = req 138 | .header(CONNECTION) 139 | .map(|connection| connection.as_str()) 140 | .unwrap_or(""); 141 | 142 | let connection_header_is_upgrade = connection_header_as_str 143 | .split(',') 144 | .any(|s| s.trim().eq_ignore_ascii_case("upgrade")); 145 | let mut close_connection = connection_header_as_str.eq_ignore_ascii_case("close"); 146 | 147 | let upgrade_requested = has_upgrade_header && connection_header_is_upgrade; 148 | 149 | let method = req.method(); 150 | 151 | // Pass the request to the endpoint and encode the response. 152 | let mut res = (self.endpoint)(req).await?; 153 | 154 | close_connection |= res 155 | .header(CONNECTION) 156 | .map(|c| c.as_str().eq_ignore_ascii_case("close")) 157 | .unwrap_or(false); 158 | 159 | let upgrade_provided = res.status() == StatusCode::SwitchingProtocols && res.has_upgrade(); 160 | 161 | let upgrade_sender = if upgrade_requested && upgrade_provided { 162 | Some(res.send_upgrade()) 163 | } else { 164 | None 165 | }; 166 | 167 | let mut encoder = Encoder::new(res, method); 168 | 169 | let bytes_written = io::copy(&mut encoder, &mut self.io).await?; 170 | log::trace!("wrote {} response bytes", bytes_written); 171 | 172 | let body_bytes_discarded = io::copy(&mut body, &mut io::sink()).await?; 173 | log::trace!( 174 | "discarded {} unread request body bytes", 175 | body_bytes_discarded 176 | ); 177 | 178 | if let Some(upgrade_sender) = upgrade_sender { 179 | upgrade_sender.send(Connection::new(self.io.clone())).await; 180 | Ok(ConnectionStatus::Close) 181 | } else if close_connection { 182 | Ok(ConnectionStatus::Close) 183 | } else { 184 | Ok(ConnectionStatus::KeepAlive) 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /tests/accept.rs: -------------------------------------------------------------------------------- 1 | mod test_utils; 2 | mod accept { 3 | use super::test_utils::TestServer; 4 | use async_h1::{client::Encoder, server::ConnectionStatus}; 5 | use async_std::io::{self, prelude::WriteExt, Cursor}; 6 | use http_types::{headers::CONNECTION, Body, Request, Response, Result}; 7 | 8 | #[async_std::test] 9 | async fn basic() -> Result<()> { 10 | let mut server = TestServer::new(|req| async { 11 | let mut response = Response::new(200); 12 | let len = req.len(); 13 | response.set_body(Body::from_reader(req, len)); 14 | Ok(response) 15 | }); 16 | 17 | let content_length = 10; 18 | 19 | let request_str = format!( 20 | "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: {}\r\n\r\n{}\r\n\r\n", 21 | content_length, 22 | std::str::from_utf8(&vec![b'|'; content_length]).unwrap() 23 | ); 24 | 25 | server.write_all(request_str.as_bytes()).await?; 26 | assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); 27 | 28 | server.close(); 29 | assert_eq!(server.accept_one().await?, ConnectionStatus::Close); 30 | 31 | assert!(server.all_read()); 32 | 33 | Ok(()) 34 | } 35 | 36 | #[async_std::test] 37 | async fn request_close() -> Result<()> { 38 | let mut server = TestServer::new(|_| async { Ok(Response::new(200)) }); 39 | 40 | server 41 | .write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: Close\r\n\r\n") 42 | .await?; 43 | 44 | assert_eq!(server.accept_one().await?, ConnectionStatus::Close); 45 | 46 | assert!(server.all_read()); 47 | 48 | Ok(()) 49 | } 50 | 51 | #[async_std::test] 52 | async fn response_close() -> Result<()> { 53 | let mut server = TestServer::new(|_| async { 54 | let mut response = Response::new(200); 55 | response.insert_header(CONNECTION, "close"); 56 | Ok(response) 57 | }); 58 | 59 | server 60 | .write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") 61 | .await?; 62 | 63 | assert_eq!(server.accept_one().await?, ConnectionStatus::Close); 64 | 65 | assert!(server.all_read()); 66 | 67 | Ok(()) 68 | } 69 | 70 | #[async_std::test] 71 | async fn keep_alive_short_fixed_length_unread_body() -> Result<()> { 72 | let mut server = TestServer::new(|_| async { Ok(Response::new(200)) }); 73 | 74 | let content_length = 10; 75 | 76 | let request_str = format!( 77 | "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: {}\r\n\r\n{}\r\n\r\n", 78 | content_length, 79 | std::str::from_utf8(&vec![b'|'; content_length]).unwrap() 80 | ); 81 | 82 | server.write_all(request_str.as_bytes()).await?; 83 | assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); 84 | 85 | server.write_all(request_str.as_bytes()).await?; 86 | assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); 87 | 88 | server.close(); 89 | assert_eq!(server.accept_one().await?, ConnectionStatus::Close); 90 | 91 | assert!(server.all_read()); 92 | 93 | Ok(()) 94 | } 95 | 96 | #[async_std::test] 97 | async fn keep_alive_short_chunked_unread_body() -> Result<()> { 98 | let mut server = TestServer::new(|_| async { Ok(Response::new(200)) }); 99 | 100 | let content_length = 100; 101 | 102 | let mut request = Request::post("http://example.com/"); 103 | request.set_body(Body::from_reader( 104 | Cursor::new(vec![b'|'; content_length]), 105 | None, 106 | )); 107 | 108 | io::copy(&mut Encoder::new(request), &mut server).await?; 109 | assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); 110 | 111 | server 112 | .write_fmt(format_args!( 113 | "GET / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n" 114 | )) 115 | .await?; 116 | assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); 117 | 118 | server.close(); 119 | assert_eq!(server.accept_one().await?, ConnectionStatus::Close); 120 | 121 | assert!(server.all_read()); 122 | 123 | Ok(()) 124 | } 125 | 126 | #[async_std::test] 127 | async fn keep_alive_long_fixed_length_unread_body() -> Result<()> { 128 | let mut server = TestServer::new(|_| async { Ok(Response::new(200)) }); 129 | 130 | let content_length = 10000; 131 | 132 | let request_str = format!( 133 | "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: {}\r\n\r\n{}\r\n\r\n", 134 | content_length, 135 | std::str::from_utf8(&vec![b'|'; content_length]).unwrap() 136 | ); 137 | 138 | server.write_all(request_str.as_bytes()).await?; 139 | assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); 140 | 141 | server.write_all(request_str.as_bytes()).await?; 142 | assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); 143 | 144 | server.close(); 145 | assert_eq!(server.accept_one().await?, ConnectionStatus::Close); 146 | 147 | assert!(server.all_read()); 148 | 149 | Ok(()) 150 | } 151 | 152 | #[async_std::test] 153 | async fn keep_alive_long_chunked_unread_body() -> Result<()> { 154 | let mut server = TestServer::new(|_| async { Ok(Response::new(200)) }); 155 | 156 | let content_length = 10000; 157 | 158 | let mut request = Request::post("http://example.com/"); 159 | request.set_body(Body::from_reader( 160 | Cursor::new(vec![b'|'; content_length]), 161 | None, 162 | )); 163 | 164 | server.write_request(request).await?; 165 | assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); 166 | 167 | server 168 | .write_fmt(format_args!( 169 | "GET / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n" 170 | )) 171 | .await?; 172 | assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); 173 | 174 | server.close(); 175 | assert_eq!(server.accept_one().await?, ConnectionStatus::Close); 176 | 177 | assert!(server.all_read()); 178 | 179 | Ok(()) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /tests/test_utils.rs: -------------------------------------------------------------------------------- 1 | use async_h1::{ 2 | client::Encoder, 3 | server::{ConnectionStatus, Server}, 4 | }; 5 | use async_std::io::{Read as AsyncRead, Write as AsyncWrite}; 6 | use http_types::{Request, Response, Result}; 7 | use std::{ 8 | fmt::{Debug, Display}, 9 | future::Future, 10 | io, 11 | pin::Pin, 12 | sync::RwLock, 13 | task::{Context, Poll, Waker}, 14 | }; 15 | 16 | use async_dup::Arc; 17 | 18 | #[pin_project::pin_project] 19 | pub struct TestServer { 20 | server: Server, 21 | #[pin] 22 | client: TestIO, 23 | } 24 | 25 | impl TestServer 26 | where 27 | F: Fn(Request) -> Fut, 28 | Fut: Future>, 29 | { 30 | #[allow(dead_code)] 31 | pub fn new(f: F) -> Self { 32 | let (client, server) = TestIO::new(); 33 | Self { 34 | server: Server::new(server, f), 35 | client, 36 | } 37 | } 38 | 39 | #[allow(dead_code)] 40 | pub async fn accept_one(&mut self) -> http_types::Result { 41 | self.server.accept_one().await 42 | } 43 | 44 | #[allow(dead_code)] 45 | pub fn close(&mut self) { 46 | self.client.close(); 47 | } 48 | 49 | #[allow(dead_code)] 50 | pub fn all_read(&self) -> bool { 51 | self.client.all_read() 52 | } 53 | 54 | #[allow(dead_code)] 55 | pub async fn write_request(&mut self, request: Request) -> io::Result<()> { 56 | async_std::io::copy(&mut Encoder::new(request), self).await?; 57 | Ok(()) 58 | } 59 | } 60 | 61 | impl AsyncRead for TestServer 62 | where 63 | F: Fn(Request) -> Fut, 64 | Fut: Future>, 65 | { 66 | fn poll_read( 67 | self: Pin<&mut Self>, 68 | cx: &mut Context<'_>, 69 | buf: &mut [u8], 70 | ) -> Poll> { 71 | self.project().client.poll_read(cx, buf) 72 | } 73 | } 74 | 75 | impl AsyncWrite for TestServer 76 | where 77 | F: Fn(Request) -> Fut, 78 | Fut: Future>, 79 | { 80 | fn poll_write( 81 | self: Pin<&mut Self>, 82 | cx: &mut Context<'_>, 83 | buf: &[u8], 84 | ) -> Poll> { 85 | self.project().client.poll_write(cx, buf) 86 | } 87 | 88 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 89 | self.project().client.poll_flush(cx) 90 | } 91 | 92 | fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 93 | self.project().client.poll_close(cx) 94 | } 95 | } 96 | 97 | /// a Test IO 98 | #[derive(Default, Clone, Debug)] 99 | pub struct TestIO { 100 | pub read: Arc, 101 | pub write: Arc, 102 | } 103 | 104 | #[derive(Default)] 105 | struct CloseableCursorInner { 106 | data: Vec, 107 | cursor: usize, 108 | waker: Option, 109 | closed: bool, 110 | } 111 | 112 | #[derive(Default)] 113 | pub struct CloseableCursor(RwLock); 114 | 115 | impl CloseableCursor { 116 | pub fn len(&self) -> usize { 117 | self.0.read().unwrap().data.len() 118 | } 119 | 120 | pub fn cursor(&self) -> usize { 121 | self.0.read().unwrap().cursor 122 | } 123 | 124 | pub fn is_empty(&self) -> bool { 125 | self.len() == 0 126 | } 127 | 128 | pub fn current(&self) -> bool { 129 | let inner = self.0.read().unwrap(); 130 | inner.data.len() == inner.cursor 131 | } 132 | 133 | pub fn close(&self) { 134 | let mut inner = self.0.write().unwrap(); 135 | inner.closed = true; 136 | if let Some(waker) = inner.waker.take() { 137 | waker.wake(); 138 | } 139 | } 140 | } 141 | 142 | impl Display for CloseableCursor { 143 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 144 | let inner = self.0.read().unwrap(); 145 | let s = std::str::from_utf8(&inner.data).unwrap_or("not utf8"); 146 | write!(f, "{}", s) 147 | } 148 | } 149 | 150 | impl TestIO { 151 | pub fn new() -> (TestIO, TestIO) { 152 | let client = Arc::new(CloseableCursor::default()); 153 | let server = Arc::new(CloseableCursor::default()); 154 | 155 | ( 156 | TestIO { 157 | read: client.clone(), 158 | write: server.clone(), 159 | }, 160 | TestIO { 161 | read: server, 162 | write: client, 163 | }, 164 | ) 165 | } 166 | 167 | pub fn all_read(&self) -> bool { 168 | self.write.current() 169 | } 170 | 171 | pub fn close(&mut self) { 172 | self.write.close(); 173 | } 174 | } 175 | 176 | impl Debug for CloseableCursor { 177 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 178 | let inner = self.0.read().unwrap(); 179 | f.debug_struct("CloseableCursor") 180 | .field( 181 | "data", 182 | &std::str::from_utf8(&inner.data).unwrap_or("not utf8"), 183 | ) 184 | .field("closed", &inner.closed) 185 | .field("cursor", &inner.cursor) 186 | .finish() 187 | } 188 | } 189 | 190 | impl AsyncRead for CloseableCursor { 191 | fn poll_read( 192 | self: Pin<&mut Self>, 193 | cx: &mut Context<'_>, 194 | buf: &mut [u8], 195 | ) -> Poll> { 196 | Pin::new(&mut &*self).poll_read(cx, buf) 197 | } 198 | } 199 | 200 | impl AsyncRead for &CloseableCursor { 201 | fn poll_read( 202 | self: Pin<&mut Self>, 203 | cx: &mut Context<'_>, 204 | buf: &mut [u8], 205 | ) -> Poll> { 206 | let mut inner = self.0.write().unwrap(); 207 | if inner.cursor < inner.data.len() { 208 | let bytes_to_copy = buf.len().min(inner.data.len() - inner.cursor); 209 | buf[..bytes_to_copy] 210 | .copy_from_slice(&inner.data[inner.cursor..inner.cursor + bytes_to_copy]); 211 | inner.cursor += bytes_to_copy; 212 | Poll::Ready(Ok(bytes_to_copy)) 213 | } else if inner.closed { 214 | Poll::Ready(Ok(0)) 215 | } else { 216 | inner.waker = Some(cx.waker().clone()); 217 | Poll::Pending 218 | } 219 | } 220 | } 221 | 222 | impl AsyncWrite for &CloseableCursor { 223 | fn poll_write( 224 | self: Pin<&mut Self>, 225 | _cx: &mut Context<'_>, 226 | buf: &[u8], 227 | ) -> Poll> { 228 | let mut inner = self.0.write().unwrap(); 229 | if inner.closed { 230 | Poll::Ready(Ok(0)) 231 | } else { 232 | inner.data.extend_from_slice(buf); 233 | if let Some(waker) = inner.waker.take() { 234 | waker.wake(); 235 | } 236 | Poll::Ready(Ok(buf.len())) 237 | } 238 | } 239 | 240 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 241 | Poll::Ready(Ok(())) 242 | } 243 | 244 | fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 245 | self.close(); 246 | Poll::Ready(Ok(())) 247 | } 248 | } 249 | 250 | impl AsyncRead for TestIO { 251 | fn poll_read( 252 | self: Pin<&mut Self>, 253 | cx: &mut Context<'_>, 254 | buf: &mut [u8], 255 | ) -> Poll> { 256 | Pin::new(&mut &*self.read).poll_read(cx, buf) 257 | } 258 | } 259 | 260 | impl AsyncWrite for TestIO { 261 | fn poll_write( 262 | self: Pin<&mut Self>, 263 | cx: &mut Context<'_>, 264 | buf: &[u8], 265 | ) -> Poll> { 266 | Pin::new(&mut &*self.write).poll_write(cx, buf) 267 | } 268 | 269 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 270 | Pin::new(&mut &*self.write).poll_flush(cx) 271 | } 272 | 273 | fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 274 | Pin::new(&mut &*self.write).poll_close(cx) 275 | } 276 | } 277 | 278 | impl std::io::Write for CloseableCursor { 279 | fn write(&mut self, buf: &[u8]) -> io::Result { 280 | self.0.write().unwrap().data.write(buf) 281 | } 282 | 283 | fn flush(&mut self) -> io::Result<()> { 284 | Ok(()) 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/server/decode.rs: -------------------------------------------------------------------------------- 1 | //! Process HTTP connections on the server. 2 | 3 | use std::str::FromStr; 4 | 5 | use async_dup::{Arc, Mutex}; 6 | use futures_lite::io::{AsyncRead as Read, AsyncWrite as Write, BufReader}; 7 | use futures_lite::prelude::*; 8 | use http_types::content::ContentLength; 9 | use http_types::headers::{EXPECT, TRANSFER_ENCODING}; 10 | use http_types::{ensure, ensure_eq, format_err}; 11 | use http_types::{Body, Method, Request, Url}; 12 | 13 | use super::body_reader::BodyReader; 14 | use crate::chunked::ChunkedDecoder; 15 | use crate::read_notifier::ReadNotifier; 16 | use crate::{MAX_HEADERS, MAX_HEAD_LENGTH}; 17 | 18 | const LF: u8 = b'\n'; 19 | 20 | /// The number returned from httparse when the request is HTTP 1.1 21 | const HTTP_1_1_VERSION: u8 = 1; 22 | 23 | const CONTINUE_HEADER_VALUE: &str = "100-continue"; 24 | const CONTINUE_RESPONSE: &[u8] = b"HTTP/1.1 100 Continue\r\n\r\n"; 25 | 26 | /// Decode an HTTP request on the server. 27 | pub async fn decode(mut io: IO) -> http_types::Result)>> 28 | where 29 | IO: Read + Write + Clone + Send + Sync + Unpin + 'static, 30 | { 31 | let mut reader = BufReader::new(io.clone()); 32 | let mut buf = Vec::new(); 33 | let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; 34 | let mut httparse_req = httparse::Request::new(&mut headers); 35 | 36 | // Keep reading bytes from the stream until we hit the end of the stream. 37 | loop { 38 | let bytes_read = reader.read_until(LF, &mut buf).await?; 39 | // No more bytes are yielded from the stream. 40 | if bytes_read == 0 { 41 | return Ok(None); 42 | } 43 | 44 | // Prevent CWE-400 DDOS with large HTTP Headers. 45 | ensure!( 46 | buf.len() < MAX_HEAD_LENGTH, 47 | "Head byte length should be less than 8kb" 48 | ); 49 | 50 | // We've hit the end delimiter of the stream. 51 | let idx = buf.len() - 1; 52 | if idx >= 3 && &buf[idx - 3..=idx] == b"\r\n\r\n" { 53 | break; 54 | } 55 | } 56 | 57 | // Convert our header buf into an httparse instance, and validate. 58 | let status = httparse_req.parse(&buf)?; 59 | 60 | ensure!(!status.is_partial(), "Malformed HTTP head"); 61 | 62 | // Convert httparse headers + body into a `http_types::Request` type. 63 | let method = httparse_req.method; 64 | let method = method.ok_or_else(|| format_err!("No method found"))?; 65 | 66 | let version = httparse_req.version; 67 | let version = version.ok_or_else(|| format_err!("No version found"))?; 68 | 69 | ensure_eq!( 70 | version, 71 | HTTP_1_1_VERSION, 72 | "Unsupported HTTP version 1.{}", 73 | version 74 | ); 75 | 76 | let url = url_from_httparse_req(&httparse_req)?; 77 | 78 | let mut req = Request::new(Method::from_str(method)?, url); 79 | 80 | req.set_version(Some(http_types::Version::Http1_1)); 81 | 82 | for header in httparse_req.headers.iter() { 83 | req.append_header(header.name, std::str::from_utf8(header.value)?); 84 | } 85 | 86 | let content_length = ContentLength::from_headers(&req)?; 87 | let transfer_encoding = req.header(TRANSFER_ENCODING); 88 | 89 | // Return a 400 status if both Content-Length and Transfer-Encoding headers 90 | // are set to prevent request smuggling attacks. 91 | // 92 | // https://tools.ietf.org/html/rfc7230#section-3.3.3 93 | http_types::ensure_status!( 94 | content_length.is_none() || transfer_encoding.is_none(), 95 | 400, 96 | "Unexpected Content-Length header" 97 | ); 98 | 99 | // Establish a channel to wait for the body to be read. This 100 | // allows us to avoid sending 100-continue in situations that 101 | // respond without reading the body, saving clients from uploading 102 | // their body. 103 | let (body_read_sender, body_read_receiver) = async_channel::bounded(1); 104 | 105 | if Some(CONTINUE_HEADER_VALUE) == req.header(EXPECT).map(|h| h.as_str()) { 106 | async_global_executor::spawn(async move { 107 | // If the client expects a 100-continue header, spawn a 108 | // task to wait for the first read attempt on the body. 109 | if let Ok(()) = body_read_receiver.recv().await { 110 | io.write_all(CONTINUE_RESPONSE).await.ok(); 111 | }; 112 | // Since the sender is moved into the Body, this task will 113 | // finish when the client disconnects, whether or not 114 | // 100-continue was sent. 115 | }) 116 | .detach(); 117 | } 118 | 119 | // Check for Transfer-Encoding 120 | if transfer_encoding 121 | .map(|te| te.as_str().eq_ignore_ascii_case("chunked")) 122 | .unwrap_or(false) 123 | { 124 | let trailer_sender = req.send_trailers(); 125 | let reader = ChunkedDecoder::new(reader, trailer_sender); 126 | let reader = Arc::new(Mutex::new(reader)); 127 | let reader_clone = reader.clone(); 128 | let reader = ReadNotifier::new(reader, body_read_sender); 129 | let reader = BufReader::new(reader); 130 | req.set_body(Body::from_reader(reader, None)); 131 | Ok(Some((req, BodyReader::Chunked(reader_clone)))) 132 | } else if let Some(len) = content_length { 133 | let len = len.len(); 134 | let reader = Arc::new(Mutex::new(reader.take(len))); 135 | req.set_body(Body::from_reader( 136 | BufReader::new(ReadNotifier::new(reader.clone(), body_read_sender)), 137 | Some(len as usize), 138 | )); 139 | Ok(Some((req, BodyReader::Fixed(reader)))) 140 | } else { 141 | Ok(Some((req, BodyReader::None))) 142 | } 143 | } 144 | 145 | fn url_from_httparse_req(req: &httparse::Request<'_, '_>) -> http_types::Result { 146 | let path = req.path.ok_or_else(|| format_err!("No uri found"))?; 147 | 148 | let host = req 149 | .headers 150 | .iter() 151 | .find(|x| x.name.eq_ignore_ascii_case("host")) 152 | .ok_or_else(|| format_err!("Mandatory Host header missing"))? 153 | .value; 154 | 155 | let host = std::str::from_utf8(host)?; 156 | 157 | if path.starts_with("http://") || path.starts_with("https://") { 158 | Ok(Url::parse(path)?) 159 | } else if path.starts_with('/') { 160 | Ok(Url::parse(&format!("http://{}{}", host, path))?) 161 | } else if req.method.unwrap().eq_ignore_ascii_case("connect") { 162 | Ok(Url::parse(&format!("http://{}/", path))?) 163 | } else { 164 | Err(format_err!("unexpected uri format")) 165 | } 166 | } 167 | 168 | #[cfg(test)] 169 | mod tests { 170 | use super::*; 171 | 172 | fn httparse_req(buf: &str, f: impl Fn(httparse::Request<'_, '_>)) { 173 | let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; 174 | let mut res = httparse::Request::new(&mut headers[..]); 175 | res.parse(buf.as_bytes()).unwrap(); 176 | f(res) 177 | } 178 | 179 | #[test] 180 | fn url_for_connect() { 181 | httparse_req( 182 | "CONNECT server.example.com:443 HTTP/1.1\r\nHost: server.example.com:443\r\n", 183 | |req| { 184 | let url = url_from_httparse_req(&req).unwrap(); 185 | assert_eq!(url.as_str(), "http://server.example.com:443/"); 186 | }, 187 | ); 188 | } 189 | 190 | #[test] 191 | fn url_for_host_plus_path() { 192 | httparse_req( 193 | "GET /some/resource HTTP/1.1\r\nHost: server.example.com:443\r\n", 194 | |req| { 195 | let url = url_from_httparse_req(&req).unwrap(); 196 | assert_eq!(url.as_str(), "http://server.example.com:443/some/resource"); 197 | }, 198 | ) 199 | } 200 | 201 | #[test] 202 | fn url_for_host_plus_absolute_url() { 203 | httparse_req( 204 | "GET http://domain.com/some/resource HTTP/1.1\r\nHost: server.example.com\r\n", 205 | |req| { 206 | let url = url_from_httparse_req(&req).unwrap(); 207 | assert_eq!(url.as_str(), "http://domain.com/some/resource"); // host header MUST be ignored according to spec 208 | }, 209 | ) 210 | } 211 | 212 | #[test] 213 | fn url_for_conflicting_connect() { 214 | httparse_req( 215 | "CONNECT server.example.com:443 HTTP/1.1\r\nHost: conflicting.host\r\n", 216 | |req| { 217 | let url = url_from_httparse_req(&req).unwrap(); 218 | assert_eq!(url.as_str(), "http://server.example.com:443/"); 219 | }, 220 | ) 221 | } 222 | 223 | #[test] 224 | fn url_for_malformed_resource_path() { 225 | httparse_req( 226 | "GET not-a-url HTTP/1.1\r\nHost: server.example.com\r\n", 227 | |req| { 228 | assert!(url_from_httparse_req(&req).is_err()); 229 | }, 230 | ) 231 | } 232 | 233 | #[test] 234 | fn url_for_double_slash_path() { 235 | httparse_req( 236 | "GET //double/slashes HTTP/1.1\r\nHost: server.example.com:443\r\n", 237 | |req| { 238 | let url = url_from_httparse_req(&req).unwrap(); 239 | assert_eq!( 240 | url.as_str(), 241 | "http://server.example.com:443//double/slashes" 242 | ); 243 | }, 244 | ) 245 | } 246 | #[test] 247 | fn url_for_triple_slash_path() { 248 | httparse_req( 249 | "GET ///triple/slashes HTTP/1.1\r\nHost: server.example.com:443\r\n", 250 | |req| { 251 | let url = url_from_httparse_req(&req).unwrap(); 252 | assert_eq!( 253 | url.as_str(), 254 | "http://server.example.com:443///triple/slashes" 255 | ); 256 | }, 257 | ) 258 | } 259 | 260 | #[test] 261 | fn url_for_query() { 262 | httparse_req( 263 | "GET /foo?bar=1 HTTP/1.1\r\nHost: server.example.com:443\r\n", 264 | |req| { 265 | let url = url_from_httparse_req(&req).unwrap(); 266 | assert_eq!(url.as_str(), "http://server.example.com:443/foo?bar=1"); 267 | }, 268 | ) 269 | } 270 | 271 | #[test] 272 | fn url_for_anchor() { 273 | httparse_req( 274 | "GET /foo?bar=1#anchor HTTP/1.1\r\nHost: server.example.com:443\r\n", 275 | |req| { 276 | let url = url_from_httparse_req(&req).unwrap(); 277 | assert_eq!( 278 | url.as_str(), 279 | "http://server.example.com:443/foo?bar=1#anchor" 280 | ); 281 | }, 282 | ) 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2019 Yoshua Wuyts 179 | Copyright 2016-2018 Michael Tilli (Pyfisch) & `httpdate` contributors 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /tests/server-chunked-encode-large.rs: -------------------------------------------------------------------------------- 1 | use async_h1::{client, server}; 2 | use http_types::Body; 3 | use http_types::Method; 4 | use http_types::Request; 5 | use http_types::Url; 6 | use http_types::{Response, Result}; 7 | use pretty_assertions::assert_eq; 8 | mod test_utils; 9 | use test_utils::TestIO; 10 | 11 | const BODY: &str = concat![ 12 | "Et provident reprehenderit accusamus dolores et voluptates sed quia. Repellendus odit porro ut et hic molestiae. Sit autem reiciendis animi fugiat deleniti vel iste. Laborum id odio ullam ut impedit dolores. Vel aperiam dolorem voluptatibus dignissimos maxime.", 13 | "Qui cumque autem debitis consequatur aliquam impedit id nostrum. Placeat error temporibus quos sed vel rerum. Fugit perferendis enim voluptatem rerum vitae dolor distinctio. Quia iusto ex enim voluptatum omnis. Nam et aperiam asperiores nesciunt eos magnam quidem et.", 14 | "Beatae et sit iure eum voluptatem accusantium quia optio. Tempora et rerum blanditiis repellendus qui est dolorem. Blanditiis deserunt qui dignissimos ad eligendi. Qui quia sequi et. Ipsa error quia quo ducimus et. Asperiores accusantium eius possimus dolore vitae iusto.", 15 | "Accusantium voluptatum sint dolor iste ut enim laborum quisquam. Iure sunt non quam quasi. Magni officiis necessitatibus omnis consequatur.", 16 | "Sed modi officia eos explicabo non recusandae praesentium. Est culpa maxime dolorem ullam. In dicta libero aut. Eum voluptatem corporis earum doloribus similique voluptate. Corporis et quia ad ut quia officia.", 17 | "Porro quod blanditiis molestiae ea. Aut eveniet laborum natus. At repudiandae eos quisquam fugit voluptatibus voluptate. Voluptatibus sint laudantium asperiores eum excepturi autem labore.", 18 | "Voluptate omnis enim nesciunt tempora. Non eum vero velit velit. Nostrum repudiandae laudantium neque iste minima dicta labore dicta. Velit animi enim ut et tenetur qui et aut. Minus sit eveniet autem repellendus accusamus.", 19 | "Deleniti qui sit modi quis et ut. Ea ab est tempore adipisci. At voluptas occaecati rem expedita nisi voluptatem iste. Dolor dolorem deleniti hic aliquam. Ullam aspernatur voluptas suscipit corrupti eius fugiat quisquam. Non quaerat dolorem doloremque modi quisquam eaque animi quae.", 20 | "Voluptas est eaque eaque et quaerat quae dolore. Qui quam et cumque quod. Dolores veritatis dignissimos possimus non. Ipsa excepturi quo autem nemo perferendis. Tempora et repellat accusamus consectetur.", 21 | "Sint et eum molestiae molestiae explicabo quae. Enim quia repellendus molestias. Harum rerum ut asperiores asperiores. Perferendis officiis iusto ab et ut nulla officia. Qui dicta magni est qui exercitationem et. Quaerat ut commodi beatae iure.", 22 | "Beatae dolor recusandae dicta vero quibusdam error. Voluptas modi aperiam id. Consequatur id quasi voluptas voluptates doloremque.", 23 | "Cum explicabo quisquam maiores autem a beatae alias. Corrupti et consequatur repellendus eos rerum iusto. Possimus ipsa totam vero in nam commodi ut eveniet. Facere recusandae commodi tenetur dolor et.", 24 | "Dolor ut ut architecto incidunt. Sunt tempora quia et similique et. Aut aut rerum soluta quibusdam. Sit deleniti ut veritatis ea nulla eius aut. Quidem doloribus beatae repudiandae ut. Consequatur eveniet consequatur consequatur sunt.", 25 | "Molestiae debitis et porro quis quas quas quod. Amet beatae placeat qui ut nihil quia. Sunt quos voluptatem id labore. Ut dolorum cupiditate ex velit occaecati velit eaque occaecati. Est ea temporibus expedita ipsum accusantium debitis qui.", 26 | "Explicabo vitae et maxime in provident natus. Nihil illo itaque eum omnis dolorum eos ratione. Corporis consequuntur nesciunt asperiores tenetur veniam est nulla.", 27 | "Ut distinctio aut dolor quia aspernatur delectus quia. Molestiae cupiditate corporis fugit asperiores sint eligendi magni. Quo necessitatibus corrupti ea tempore officiis est minus. Nesciunt quos qui minima nostrum nobis qui earum. Temporibus doloremque sed at.", 28 | "Qui quas occaecati et. Possimus corrupti eaque quis sed accusantium voluptatum ducimus laborum. Alias sapiente et exercitationem ex sequi accusamus ea. Quis id aspernatur soluta et quisquam animi. Aspernatur quasi autem qui. Est dolores iusto perspiciatis.", 29 | "Itaque incidunt numquam dolores quaerat. Assumenda rerum porro itaque. Ut ratione temporibus occaecati rerum qui commodi.", 30 | "Nemo nemo iste qui voluptas itaque. Quae quis qui qui cum quod natus itaque est. Dolores voluptate sapiente ipsa eveniet doloremque laboriosam velit sunt. Optio voluptatum doloremque tenetur voluptate.", 31 | "Recusandae nihil sunt similique minima quis temporibus cum. Laboriosam atque aut tenetur ducimus et vitae. Ducimus qui debitis ut. Non ducimus incidunt optio voluptatum fuga non fugit veritatis. Ut laudantium est minima corporis voluptas inventore qui eum. Rem id aut amet ut.", 32 | "Et provident reprehenderit accusamus dolores et voluptates sed quia. Repellendus odit porro ut et hic molestiae. Sit autem reiciendis animi fugiat deleniti vel iste. Laborum id odio ullam ut impedit dolores. Vel aperiam dolorem voluptatibus dignissimos maxime.", 33 | "Qui cumque autem debitis consequatur aliquam impedit id nostrum. Placeat error temporibus quos sed vel rerum. Fugit perferendis enim voluptatem rerum vitae dolor distinctio. Quia iusto ex enim voluptatum omnis. Nam et aperiam asperiores nesciunt eos magnam quidem et.", 34 | "Beatae et sit iure eum voluptatem accusantium quia optio. Tempora et rerum blanditiis repellendus qui est dolorem. Blanditiis deserunt qui dignissimos ad eligendi. Qui quia sequi et. Ipsa error quia quo ducimus et. Asperiores accusantium eius possimus dolore vitae iusto.", 35 | "Accusantium voluptatum sint dolor iste ut enim laborum quisquam. Iure sunt non quam quasi. Magni officiis necessitatibus omnis consequatur.", 36 | "Sed modi officia eos explicabo non recusandae praesentium. Est culpa maxime dolorem ullam. In dicta libero aut. Eum voluptatem corporis earum doloribus similique voluptate. Corporis et quia ad ut quia officia.", 37 | "Porro quod blanditiis molestiae ea. Aut eveniet laborum natus. At repudiandae eos quisquam fugit voluptatibus voluptate. Voluptatibus sint laudantium asperiores eum excepturi autem labore.", 38 | "Voluptate omnis enim nesciunt tempora. Non eum vero velit velit. Nostrum repudiandae laudantium neque iste minima dicta labore dicta. Velit animi enim ut et tenetur qui et aut. Minus sit eveniet autem repellendus accusamus.", 39 | "Deleniti qui sit modi quis et ut. Ea ab est tempore adipisci. At voluptas occaecati rem expedita nisi voluptatem iste. Dolor dolorem deleniti hic aliquam. Ullam aspernatur voluptas suscipit corrupti eius fugiat quisquam. Non quaerat dolorem doloremque modi quisquam eaque animi quae.", 40 | "Voluptas est eaque eaque et quaerat quae dolore. Qui quam et cumque quod. Dolores veritatis dignissimos possimus non. Ipsa excepturi quo autem nemo perferendis. Tempora et repellat accusamus consectetur.", 41 | "Sint et eum molestiae molestiae explicabo quae. Enim quia repellendus molestias. Harum rerum ut asperiores asperiores. Perferendis officiis iusto ab et ut nulla officia. Qui dicta magni est qui exercitationem et. Quaerat ut commodi beatae iure.", 42 | "Beatae dolor recusandae dicta vero quibusdam error. Voluptas modi aperiam id. Consequatur id quasi voluptas voluptates doloremque.", 43 | "Cum explicabo quisquam maiores autem a beatae alias. Corrupti et consequatur repellendus eos rerum iusto. Possimus ipsa totam vero in nam commodi ut eveniet. Facere recusandae commodi tenetur dolor et.", 44 | "Explicabo vitae et maxime in provident natus. Nihil illo itaque eum omnis dolorum eos ratione. Corporis consequuntur nesciunt asperiores tenetur veniam est nulla.", 45 | "Ut distinctio aut dolor quia aspernatur delectus quia. Molestiae cupiditate corporis fugit asperiores sint eligendi magni. Quo necessitatibus corrupti ea tempore officiis est minus. Nesciunt quos qui minima nostrum nobis qui earum. Temporibus doloremque sed at.", 46 | "Qui quas occaecati et. Possimus corrupti eaque quis sed accusantium voluptatum ducimus laborum. Alias sapiente et exercitationem ex sequi accusamus ea. Quis id aspernatur soluta et quisquam animi. Aspernatur quasi autem qui. Est dolores iusto perspiciatis.", 47 | "Itaque incidunt numquam dolores quaerat. Assumenda rerum porro itaque. Ut ratione temporibus occaecati rerum qui commodi.", 48 | "Nemo nemo iste qui voluptas itaque. Quae quis qui qui cum quod natus itaque est. Dolores voluptate sapiente ipsa eveniet doloremque laboriosam velit sunt. Optio voluptatum doloremque tenetur voluptate.", 49 | "Recusandae nihil sunt similique minima quis temporibus cum. Laboriosam atque aut tenetur ducimus et vitae. Ducimus qui debitis ut. Non ducimus incidunt optio voluptatum fuga non fugit veritatis. Ut laudantium est minima corporis voluptas inventore qui eum. Rem id aut amet ut.", 50 | "Et provident reprehenderit accusamus dolores et voluptates sed quia. Repellendus odit porro ut et hic molestiae. Sit autem reiciendis animi fugiat deleniti vel iste. Laborum id odio ullam ut impedit dolores. Vel aperiam dolorem voluptatibus dignissimos maxime.", 51 | "Qui cumque autem debitis consequatur aliquam impedit id nostrum. Placeat error temporibus quos sed vel rerum. Fugit perferendis enim voluptatem rerum vitae dolor distinctio. Quia iusto ex enim voluptatum omnis. Nam et aperiam asperiores nesciunt eos magnam quidem et.", 52 | "Accusantium voluptatum sint dolor iste ut enim laborum quisquam. Iure sunt non quam quasi. Magni officiis necessitatibus omnis consequatur.", 53 | "Sed modi officia eos explicabo non recusandae praesentium. Est culpa maxime dolorem ullam. In dicta libero aut. Eum voluptatem corporis earum doloribus similique voluptate. Corporis et quia ad ut quia officia.", 54 | "Porro quod blanditiis molestiae ea. Aut eveniet laborum natus. At repudiandae eos quisquam fugit voluptatibus voluptate. Voluptatibus sint laudantium asperiores eum excepturi autem labore.", 55 | "Voluptate omnis enim nesciunt tempora. Non eum vero velit velit. Nostrum repudiandae laudantium neque iste minima dicta labore dicta. Velit animi enim ut et tenetur qui et aut. Minus sit eveniet autem repellendus accusamus.", 56 | "Deleniti qui sit modi quis et ut. Ea ab est tempore adipisci. At voluptas occaecati rem expedita nisi voluptatem iste. Dolor dolorem deleniti hic aliquam. Ullam aspernatur voluptas suscipit corrupti eius fugiat quisquam. Non quaerat dolorem doloremque modi quisquam eaque animi quae.", 57 | "Voluptas est eaque eaque et quaerat quae dolore. Qui quam et cumque quod. Dolores veritatis dignissimos possimus non. Ipsa excepturi quo autem nemo perferendis. Tempora et repellat accusamus consectetur.", 58 | "Sint et eum molestiae molestiae explicabo quae. Enim quia repellendus molestias. Harum rerum ut asperiores asperiores. Perferendis officiis iusto ab et ut nulla officia. Qui dicta magni est qui exercitationem et. Quaerat ut commodi beatae iure.", 59 | "Beatae dolor recusandae dicta vero quibusdam error. Voluptas modi aperiam id. Consequatur id quasi voluptas voluptates doloremque.", 60 | "Cum explicabo quisquam maiores autem a beatae alias. Corrupti et consequatur repellendus eos rerum iusto. Possimus ipsa totam vero in nam commodi ut eveniet. Facere recusandae commodi tenetur dolor et.", 61 | "Dolor ut ut architecto incidunt. Sunt tempora quia et similique et. Aut aut rerum soluta quibusdam. Sit deleniti ut veritatis ea nulla eius aut. Quidem doloribus beatae repudiandae ut. Consequatur eveniet consequatur consequatur sunt.", 62 | "Molestiae debitis et porro quis quas quas quod. Amet beatae placeat qui ut nihil quia. Sunt quos voluptatem id labore. Ut dolorum cupiditate ex velit occaecati velit eaque occaecati. Est ea temporibus expedita ipsum accusantium debitis qui.", 63 | "Explicabo vitae et maxime in provident natus. Nihil illo itaque eum omnis dolorum eos ratione. Corporis consequuntur nesciunt asperiores tenetur veniam est nulla.", 64 | "Ut distinctio aut dolor quia aspernatur delectus quia. Molestiae cupiditate corporis fugit asperiores sint eligendi magni. Quo necessitatibus corrupti ea tempore officiis est minus. Nesciunt quos qui minima nostrum nobis qui earum. Temporibus doloremque sed at.", 65 | "Qui quas occaecati et. Possimus corrupti eaque quis sed accusantium voluptatum ducimus laborum. Alias sapiente et exercitationem ex sequi accusamus ea. Quis id aspernatur soluta et quisquam animi. Aspernatur quasi autem qui. Est dolores iusto perspiciatis.", 66 | "Itaque incidunt numquam dolores quaerat. Assumenda rerum porro itaque. Ut ratione temporibus occaecati rerum qui commodi.", 67 | "Nemo nemo iste qui voluptas itaque. Quae quis qui qui cum quod natus itaque est. Dolores voluptate sapiente ipsa eveniet doloremque laboriosam velit sunt. Optio voluptatum doloremque tenetur voluptate.", 68 | ]; 69 | 70 | #[async_std::test] 71 | async fn server_chunked_large() -> Result<()> { 72 | let mut request = Request::new(Method::Post, Url::parse("http://domain.com").unwrap()); 73 | // request.set_body(Body::from_reader(Cursor::new(BODY), None)); 74 | request.set_body(Body::from_string(String::from(BODY))); 75 | 76 | let (mut client, server) = TestIO::new(); 77 | async_std::io::copy(&mut client::Encoder::new(request), &mut client).await?; 78 | 79 | let (request, _) = server::decode(server).await?.unwrap(); 80 | 81 | let mut response = Response::new(200); 82 | response.set_body(Body::from_reader(request, None)); 83 | 84 | let response_encoder = server::Encoder::new(response, Method::Get); 85 | 86 | let mut response = client::decode(response_encoder).await?; 87 | 88 | assert_eq!(response.body_string().await?, BODY); 89 | Ok(()) 90 | } 91 | -------------------------------------------------------------------------------- /src/chunked/decoder.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::future::Future; 3 | use std::pin::Pin; 4 | use std::task::{Context, Poll}; 5 | 6 | use futures_lite::io::{self, AsyncRead as Read}; 7 | use futures_lite::ready; 8 | use http_types::trailers::{Sender, Trailers}; 9 | 10 | /// Decodes a chunked body according to 11 | /// https://tools.ietf.org/html/rfc7230#section-4.1 12 | #[derive(Debug)] 13 | pub struct ChunkedDecoder { 14 | /// The underlying stream 15 | inner: R, 16 | /// Current state. 17 | state: State, 18 | /// Current chunk size (increased while parsing size, decreased while reading chunk) 19 | chunk_size: u64, 20 | /// Trailer channel sender. 21 | trailer_sender: Option, 22 | } 23 | 24 | impl ChunkedDecoder { 25 | pub(crate) fn new(inner: R, trailer_sender: Sender) -> Self { 26 | ChunkedDecoder { 27 | inner, 28 | state: State::ChunkSize, 29 | chunk_size: 0, 30 | trailer_sender: Some(trailer_sender), 31 | } 32 | } 33 | } 34 | 35 | /// Decoder state. 36 | enum State { 37 | /// Parsing bytes from a chunk size 38 | ChunkSize, 39 | /// Expecting the \n at the end of a chunk size 40 | ChunkSizeExpectLf, 41 | /// Parsing the chunk body 42 | ChunkBody, 43 | /// Expecting the \r at the end of a chunk body 44 | ChunkBodyExpectCr, 45 | /// Expecting the \n at the end of a chunk body 46 | ChunkBodyExpectLf, 47 | /// Parsing trailers. 48 | Trailers(usize, Box<[u8; 8192]>), 49 | /// Sending trailers over the channel. 50 | TrailerSending(Pin + 'static + Send + Sync>>), 51 | /// All is said and done. 52 | Done, 53 | } 54 | 55 | impl fmt::Debug for State { 56 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 57 | match self { 58 | State::ChunkSize => write!(f, "State::ChunkSize"), 59 | State::ChunkSizeExpectLf => write!(f, "State::ChunkSizeExpectLf"), 60 | State::ChunkBody => write!(f, "State::ChunkBody"), 61 | State::ChunkBodyExpectCr => write!(f, "State::ChunkBodyExpectCr"), 62 | State::ChunkBodyExpectLf => write!(f, "State::ChunkBodyExpectLf"), 63 | State::Trailers(len, _) => write!(f, "State::Trailers({}, _)", len), 64 | State::TrailerSending(_) => write!(f, "State::TrailerSending"), 65 | State::Done => write!(f, "State::Done"), 66 | } 67 | } 68 | } 69 | 70 | impl ChunkedDecoder { 71 | fn poll_read_byte(&mut self, cx: &mut Context<'_>) -> Poll> { 72 | let mut byte = [0u8]; 73 | if ready!(Pin::new(&mut self.inner).poll_read(cx, &mut byte))? == 1 { 74 | Poll::Ready(Ok(byte[0])) 75 | } else { 76 | eof() 77 | } 78 | } 79 | 80 | fn expect_byte( 81 | &mut self, 82 | cx: &mut Context<'_>, 83 | expected_byte: u8, 84 | expected: &'static str, 85 | ) -> Poll> { 86 | let byte = ready!(self.poll_read_byte(cx))?; 87 | if byte == expected_byte { 88 | Poll::Ready(Ok(())) 89 | } else { 90 | unexpected(byte, expected) 91 | } 92 | } 93 | 94 | fn send_trailers(&mut self, trailers: Trailers) { 95 | let sender = self 96 | .trailer_sender 97 | .take() 98 | .expect("invalid chunked state, tried sending multiple trailers"); 99 | let fut = Box::pin(sender.send(trailers)); 100 | self.state = State::TrailerSending(fut); 101 | } 102 | } 103 | 104 | fn eof() -> Poll> { 105 | Poll::Ready(Err(io::Error::new( 106 | io::ErrorKind::UnexpectedEof, 107 | "Unexpected EOF when decoding chunked data", 108 | ))) 109 | } 110 | 111 | fn unexpected(byte: u8, expected: &'static str) -> Poll> { 112 | Poll::Ready(Err(io::Error::new( 113 | io::ErrorKind::InvalidData, 114 | format!("Unexpected byte {}; expected {}", byte, expected), 115 | ))) 116 | } 117 | 118 | fn overflow() -> io::Error { 119 | io::Error::new(io::ErrorKind::InvalidData, "Chunk size overflowed 64 bits") 120 | } 121 | 122 | impl Read for ChunkedDecoder { 123 | #[allow(missing_doc_code_examples)] 124 | fn poll_read( 125 | mut self: Pin<&mut Self>, 126 | cx: &mut Context<'_>, 127 | buf: &mut [u8], 128 | ) -> Poll> { 129 | let this = &mut *self; 130 | 131 | loop { 132 | match this.state { 133 | State::ChunkSize => { 134 | let byte = ready!(this.poll_read_byte(cx))?; 135 | let digit = match byte { 136 | b'0'..=b'9' => byte - b'0', 137 | b'a'..=b'f' => 10 + byte - b'a', 138 | b'A'..=b'F' => 10 + byte - b'A', 139 | b'\r' => { 140 | this.state = State::ChunkSizeExpectLf; 141 | continue; 142 | } 143 | _ => { 144 | return unexpected(byte, "hex digit or CR"); 145 | } 146 | }; 147 | this.chunk_size = this 148 | .chunk_size 149 | .checked_mul(16) 150 | .ok_or_else(overflow)? 151 | .checked_add(digit as u64) 152 | .ok_or_else(overflow)?; 153 | } 154 | State::ChunkSizeExpectLf => { 155 | ready!(this.expect_byte(cx, b'\n', "LF"))?; 156 | if this.chunk_size == 0 { 157 | this.state = State::Trailers(0, Box::new([0u8; 8192])); 158 | } else { 159 | this.state = State::ChunkBody; 160 | } 161 | } 162 | State::ChunkBody => { 163 | let max_bytes = std::cmp::min( 164 | buf.len(), 165 | std::cmp::min(this.chunk_size, usize::MAX as u64) as usize, 166 | ); 167 | let bytes_read = 168 | ready!(Pin::new(&mut this.inner).poll_read(cx, &mut buf[..max_bytes]))?; 169 | this.chunk_size -= bytes_read as u64; 170 | if bytes_read == 0 { 171 | return eof(); 172 | } else if this.chunk_size == 0 { 173 | this.state = State::ChunkBodyExpectCr; 174 | } 175 | return Poll::Ready(Ok(bytes_read)); 176 | } 177 | State::ChunkBodyExpectCr => { 178 | ready!(this.expect_byte(cx, b'\r', "CR"))?; 179 | this.state = State::ChunkBodyExpectLf; 180 | } 181 | State::ChunkBodyExpectLf => { 182 | ready!(this.expect_byte(cx, b'\n', "LF"))?; 183 | this.state = State::ChunkSize; 184 | } 185 | State::Trailers(ref mut len, ref mut buf) => { 186 | let bytes_read = 187 | ready!(Pin::new(&mut this.inner).poll_read(cx, &mut buf[*len..]))?; 188 | *len += bytes_read; 189 | let len = *len; 190 | if len == 0 { 191 | this.send_trailers(Trailers::new()); 192 | continue; 193 | } 194 | if bytes_read == 0 { 195 | return eof(); 196 | } 197 | let mut headers = [httparse::EMPTY_HEADER; 16]; 198 | let parse_result = httparse::parse_headers(&buf[..len], &mut headers) 199 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; 200 | use httparse::Status; 201 | match parse_result { 202 | Status::Partial => { 203 | if len == buf.len() { 204 | return eof(); 205 | } else { 206 | return Poll::Pending; 207 | } 208 | } 209 | Status::Complete((offset, headers)) => { 210 | if offset != len { 211 | return unexpected(buf[offset], "end of trailers"); 212 | } 213 | let mut trailers = Trailers::new(); 214 | for header in headers { 215 | trailers.insert( 216 | header.name, 217 | String::from_utf8_lossy(header.value).as_ref(), 218 | ); 219 | } 220 | this.send_trailers(trailers); 221 | } 222 | } 223 | } 224 | State::TrailerSending(ref mut fut) => { 225 | ready!(Pin::new(fut).poll(cx)); 226 | this.state = State::Done; 227 | } 228 | State::Done => return Poll::Ready(Ok(0)), 229 | } 230 | } 231 | } 232 | } 233 | 234 | #[cfg(test)] 235 | mod tests { 236 | use super::*; 237 | use async_std::prelude::*; 238 | 239 | #[test] 240 | fn test_chunked_wiki() { 241 | async_std::task::block_on(async move { 242 | let input = async_std::io::Cursor::new( 243 | "4\r\n\ 244 | Wiki\r\n\ 245 | 5\r\n\ 246 | pedia\r\n\ 247 | E\r\n in\r\n\ 248 | \r\n\ 249 | chunks.\r\n\ 250 | 0\r\n\ 251 | \r\n" 252 | .as_bytes(), 253 | ); 254 | 255 | let (s, _r) = async_channel::bounded(1); 256 | let sender = Sender::new(s); 257 | let mut decoder = ChunkedDecoder::new(input, sender); 258 | 259 | let mut output = String::new(); 260 | decoder.read_to_string(&mut output).await.unwrap(); 261 | assert_eq!( 262 | output, 263 | "Wikipedia in\r\n\ 264 | \r\n\ 265 | chunks." 266 | ); 267 | }); 268 | } 269 | 270 | #[test] 271 | fn test_chunked_big() { 272 | async_std::task::block_on(async move { 273 | let mut input: Vec = b"800\r\n".to_vec(); 274 | input.extend(vec![b'X'; 2048]); 275 | input.extend(b"\r\n1800\r\n"); 276 | input.extend(vec![b'Y'; 6144]); 277 | input.extend(b"\r\n800\r\n"); 278 | input.extend(vec![b'Z'; 2048]); 279 | input.extend(b"\r\n0\r\n\r\n"); 280 | 281 | let (s, _r) = async_channel::bounded(1); 282 | let sender = Sender::new(s); 283 | let mut decoder = ChunkedDecoder::new(async_std::io::Cursor::new(input), sender); 284 | 285 | let mut output = String::new(); 286 | decoder.read_to_string(&mut output).await.unwrap(); 287 | 288 | let mut expected = vec![b'X'; 2048]; 289 | expected.extend(vec![b'Y'; 6144]); 290 | expected.extend(vec![b'Z'; 2048]); 291 | assert_eq!(output.len(), 10240); 292 | assert_eq!(output.as_bytes(), expected.as_slice()); 293 | }); 294 | } 295 | 296 | #[test] 297 | fn test_chunked_mdn() { 298 | async_std::task::block_on(async move { 299 | let input = async_std::io::Cursor::new( 300 | "7\r\n\ 301 | Mozilla\r\n\ 302 | 9\r\n\ 303 | Developer\r\n\ 304 | 7\r\n\ 305 | Network\r\n\ 306 | 0\r\n\ 307 | Expires: Wed, 21 Oct 2015 07:28:00 GMT\r\n\ 308 | \r\n" 309 | .as_bytes(), 310 | ); 311 | let (s, r) = async_channel::bounded(1); 312 | let sender = Sender::new(s); 313 | let mut decoder = ChunkedDecoder::new(input, sender); 314 | 315 | let mut output = String::new(); 316 | decoder.read_to_string(&mut output).await.unwrap(); 317 | assert_eq!(output, "MozillaDeveloperNetwork"); 318 | 319 | let trailers = r.recv().await.unwrap(); 320 | assert_eq!(trailers.iter().count(), 1); 321 | assert_eq!(trailers["Expires"], "Wed, 21 Oct 2015 07:28:00 GMT"); 322 | }); 323 | } 324 | 325 | #[test] 326 | fn test_ff7() { 327 | async_std::task::block_on(async move { 328 | let mut input: Vec = b"FF7\r\n".to_vec(); 329 | input.extend(vec![b'X'; 0xFF7]); 330 | input.extend(b"\r\n4\r\n"); 331 | input.extend(vec![b'Y'; 4]); 332 | input.extend(b"\r\n0\r\n\r\n"); 333 | 334 | let (s, _r) = async_channel::bounded(1); 335 | let sender = Sender::new(s); 336 | let mut decoder = ChunkedDecoder::new(async_std::io::Cursor::new(input), sender); 337 | 338 | let mut output = String::new(); 339 | decoder.read_to_string(&mut output).await.unwrap(); 340 | assert_eq!( 341 | output, 342 | "X".to_string().repeat(0xFF7) + &"Y".to_string().repeat(4) 343 | ); 344 | }); 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/date.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display, Formatter}; 2 | use std::str::{from_utf8, FromStr}; 3 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 4 | 5 | use http_types::{bail, ensure, format_err}; 6 | 7 | const IMF_FIXDATE_LENGTH: usize = 29; 8 | const RFC850_MAX_LENGTH: usize = 23; 9 | const ASCTIME_LENGTH: usize = 24; 10 | 11 | const YEAR_9999_SECONDS: u64 = 253402300800; 12 | const SECONDS_IN_DAY: u64 = 86400; 13 | const SECONDS_IN_HOUR: u64 = 3600; 14 | 15 | /// Format using the `Display` trait. 16 | /// Convert timestamp into/from `SytemTime` to use. 17 | /// Supports comparison and sorting. 18 | #[derive(Copy, Clone, Debug, Eq)] 19 | pub struct HttpDate { 20 | /// 0...59 21 | second: u8, 22 | /// 0...59 23 | minute: u8, 24 | /// 0...23 25 | hour: u8, 26 | /// 1...31 27 | day: u8, 28 | /// 1...12 29 | month: u8, 30 | /// 1970...9999 31 | year: u16, 32 | /// 1...7 33 | week_day: u8, 34 | } 35 | 36 | /// Parse a date from an HTTP header field. 37 | /// 38 | /// Supports the preferred IMF-fixdate and the legacy RFC 805 and 39 | /// ascdate formats. Two digit years are mapped to dates between 40 | /// 1970 and 2069. 41 | #[allow(dead_code)] 42 | pub(crate) fn parse_http_date(s: &str) -> http_types::Result { 43 | s.parse::().map(|d| d.into()) 44 | } 45 | 46 | /// Format a date to be used in a HTTP header field. 47 | /// 48 | /// Dates are formatted as IMF-fixdate: `Fri, 15 May 2015 15:34:21 GMT`. 49 | pub(crate) fn fmt_http_date(d: SystemTime) -> String { 50 | format!("{}", HttpDate::from(d)) 51 | } 52 | 53 | impl HttpDate { 54 | fn is_valid(self) -> bool { 55 | self.second < 60 56 | && self.minute < 60 57 | && self.hour < 24 58 | && self.day > 0 59 | && self.day < 32 60 | && self.month > 0 61 | && self.month <= 12 62 | && self.year >= 1970 63 | && self.year <= 9999 64 | && self.week_day >= 1 65 | && self.week_day < 8 66 | } 67 | } 68 | 69 | fn parse_imf_fixdate(s: &[u8]) -> http_types::Result { 70 | // Example: `Sun, 06 Nov 1994 08:49:37 GMT` 71 | if s.len() != IMF_FIXDATE_LENGTH 72 | || &s[25..] != b" GMT" 73 | || s[16] != b' ' 74 | || s[19] != b':' 75 | || s[22] != b':' 76 | { 77 | bail!("Date time not in imf fixdate format"); 78 | } 79 | Ok(HttpDate { 80 | second: from_utf8(&s[23..25])?.parse()?, 81 | minute: from_utf8(&s[20..22])?.parse()?, 82 | hour: from_utf8(&s[17..19])?.parse()?, 83 | day: from_utf8(&s[5..7])?.parse()?, 84 | month: match &s[7..12] { 85 | b" Jan " => 1, 86 | b" Feb " => 2, 87 | b" Mar " => 3, 88 | b" Apr " => 4, 89 | b" May " => 5, 90 | b" Jun " => 6, 91 | b" Jul " => 7, 92 | b" Aug " => 8, 93 | b" Sep " => 9, 94 | b" Oct " => 10, 95 | b" Nov " => 11, 96 | b" Dec " => 12, 97 | _ => bail!("Invalid Month"), 98 | }, 99 | year: from_utf8(&s[12..16])?.parse()?, 100 | week_day: match &s[..5] { 101 | b"Mon, " => 1, 102 | b"Tue, " => 2, 103 | b"Wed, " => 3, 104 | b"Thu, " => 4, 105 | b"Fri, " => 5, 106 | b"Sat, " => 6, 107 | b"Sun, " => 7, 108 | _ => bail!("Invalid Day"), 109 | }, 110 | }) 111 | } 112 | 113 | fn parse_rfc850_date(s: &[u8]) -> http_types::Result { 114 | // Example: `Sunday, 06-Nov-94 08:49:37 GMT` 115 | ensure!( 116 | s.len() >= RFC850_MAX_LENGTH, 117 | "Date time not in rfc850 format" 118 | ); 119 | 120 | fn week_day<'a>(s: &'a [u8], week_day: u8, name: &'static [u8]) -> Option<(u8, &'a [u8])> { 121 | if &s[0..name.len()] == name { 122 | return Some((week_day, &s[name.len()..])); 123 | } 124 | None 125 | } 126 | let (week_day, s) = week_day(s, 1, b"Monday, ") 127 | .or_else(|| week_day(s, 2, b"Tuesday, ")) 128 | .or_else(|| week_day(s, 3, b"Wednesday, ")) 129 | .or_else(|| week_day(s, 4, b"Thursday, ")) 130 | .or_else(|| week_day(s, 5, b"Friday, ")) 131 | .or_else(|| week_day(s, 6, b"Saturday, ")) 132 | .or_else(|| week_day(s, 7, b"Sunday, ")) 133 | .ok_or_else(|| format_err!("Invalid day"))?; 134 | if s.len() != 22 || s[12] != b':' || s[15] != b':' || &s[18..22] != b" GMT" { 135 | bail!("Date time not in rfc950 fmt"); 136 | } 137 | let mut year = from_utf8(&s[7..9])?.parse::()?; 138 | if year < 70 { 139 | year += 2000; 140 | } else { 141 | year += 1900; 142 | } 143 | Ok(HttpDate { 144 | second: from_utf8(&s[16..18])?.parse()?, 145 | minute: from_utf8(&s[13..15])?.parse()?, 146 | hour: from_utf8(&s[10..12])?.parse()?, 147 | day: from_utf8(&s[0..2])?.parse()?, 148 | month: match &s[2..7] { 149 | b"-Jan-" => 1, 150 | b"-Feb-" => 2, 151 | b"-Mar-" => 3, 152 | b"-Apr-" => 4, 153 | b"-May-" => 5, 154 | b"-Jun-" => 6, 155 | b"-Jul-" => 7, 156 | b"-Aug-" => 8, 157 | b"-Sep-" => 9, 158 | b"-Oct-" => 10, 159 | b"-Nov-" => 11, 160 | b"-Dec-" => 12, 161 | _ => bail!("Invalid month"), 162 | }, 163 | year, 164 | week_day, 165 | }) 166 | } 167 | 168 | fn parse_asctime(s: &[u8]) -> http_types::Result { 169 | // Example: `Sun Nov 6 08:49:37 1994` 170 | if s.len() != ASCTIME_LENGTH || s[10] != b' ' || s[13] != b':' || s[16] != b':' || s[19] != b' ' 171 | { 172 | bail!("Date time not in asctime format"); 173 | } 174 | Ok(HttpDate { 175 | second: from_utf8(&s[17..19])?.parse()?, 176 | minute: from_utf8(&s[14..16])?.parse()?, 177 | hour: from_utf8(&s[11..13])?.parse()?, 178 | day: { 179 | let x = &s[8..10]; 180 | from_utf8(if x[0] == b' ' { &x[1..2] } else { x })?.parse()? 181 | }, 182 | month: match &s[4..8] { 183 | b"Jan " => 1, 184 | b"Feb " => 2, 185 | b"Mar " => 3, 186 | b"Apr " => 4, 187 | b"May " => 5, 188 | b"Jun " => 6, 189 | b"Jul " => 7, 190 | b"Aug " => 8, 191 | b"Sep " => 9, 192 | b"Oct " => 10, 193 | b"Nov " => 11, 194 | b"Dec " => 12, 195 | _ => bail!("Invalid month"), 196 | }, 197 | year: from_utf8(&s[20..24])?.parse()?, 198 | week_day: match &s[0..4] { 199 | b"Mon " => 1, 200 | b"Tue " => 2, 201 | b"Wed " => 3, 202 | b"Thu " => 4, 203 | b"Fri " => 5, 204 | b"Sat " => 6, 205 | b"Sun " => 7, 206 | _ => bail!("Invalid day"), 207 | }, 208 | }) 209 | } 210 | 211 | impl From for HttpDate { 212 | fn from(system_time: SystemTime) -> Self { 213 | let dur = system_time 214 | .duration_since(UNIX_EPOCH) 215 | .expect("all times should be after the epoch"); 216 | let secs_since_epoch = dur.as_secs(); 217 | 218 | if secs_since_epoch >= YEAR_9999_SECONDS { 219 | // year 9999 220 | panic!("date must be before year 9999"); 221 | } 222 | 223 | /* 2000-03-01 (mod 400 year, immediately after feb29 */ 224 | const LEAPOCH: i64 = 11017; 225 | const DAYS_PER_400Y: i64 = 365 * 400 + 97; 226 | const DAYS_PER_100Y: i64 = 365 * 100 + 24; 227 | const DAYS_PER_4Y: i64 = 365 * 4 + 1; 228 | 229 | let days = (secs_since_epoch / SECONDS_IN_DAY) as i64 - LEAPOCH; 230 | let secs_of_day = secs_since_epoch % SECONDS_IN_DAY; 231 | 232 | let mut qc_cycles = days / DAYS_PER_400Y; 233 | let mut remdays = days % DAYS_PER_400Y; 234 | 235 | if remdays < 0 { 236 | remdays += DAYS_PER_400Y; 237 | qc_cycles -= 1; 238 | } 239 | 240 | let mut c_cycles = remdays / DAYS_PER_100Y; 241 | if c_cycles == 4 { 242 | c_cycles -= 1; 243 | } 244 | remdays -= c_cycles * DAYS_PER_100Y; 245 | 246 | let mut q_cycles = remdays / DAYS_PER_4Y; 247 | if q_cycles == 25 { 248 | q_cycles -= 1; 249 | } 250 | remdays -= q_cycles * DAYS_PER_4Y; 251 | 252 | let mut remyears = remdays / 365; 253 | if remyears == 4 { 254 | remyears -= 1; 255 | } 256 | remdays -= remyears * 365; 257 | 258 | let mut year = 2000 + remyears + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles; 259 | 260 | let months = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29]; 261 | let mut month = 0; 262 | for month_len in months.iter() { 263 | month += 1; 264 | if remdays < *month_len { 265 | break; 266 | } 267 | remdays -= *month_len; 268 | } 269 | let mday = remdays + 1; 270 | let month = if month + 2 > 12 { 271 | year += 1; 272 | month - 10 273 | } else { 274 | month + 2 275 | }; 276 | 277 | let mut week_day = (3 + days) % 7; 278 | if week_day <= 0 { 279 | week_day += 7 280 | }; 281 | 282 | HttpDate { 283 | second: (secs_of_day % 60) as u8, 284 | minute: ((secs_of_day % SECONDS_IN_HOUR) / 60) as u8, 285 | hour: (secs_of_day / SECONDS_IN_HOUR) as u8, 286 | day: mday as u8, 287 | month: month as u8, 288 | year: year as u16, 289 | week_day: week_day as u8, 290 | } 291 | } 292 | } 293 | 294 | impl From for SystemTime { 295 | fn from(http_date: HttpDate) -> Self { 296 | let leap_years = ((http_date.year - 1) - 1968) / 4 - ((http_date.year - 1) - 1900) / 100 297 | + ((http_date.year - 1) - 1600) / 400; 298 | let mut ydays = match http_date.month { 299 | 1 => 0, 300 | 2 => 31, 301 | 3 => 59, 302 | 4 => 90, 303 | 5 => 120, 304 | 6 => 151, 305 | 7 => 181, 306 | 8 => 212, 307 | 9 => 243, 308 | 10 => 273, 309 | 11 => 304, 310 | 12 => 334, 311 | _ => unreachable!(), 312 | } + http_date.day as u64 313 | - 1; 314 | if is_leap_year(http_date.year) && http_date.month > 2 { 315 | ydays += 1; 316 | } 317 | let days = (http_date.year as u64 - 1970) * 365 + leap_years as u64 + ydays; 318 | UNIX_EPOCH 319 | + Duration::from_secs( 320 | http_date.second as u64 321 | + http_date.minute as u64 * 60 322 | + http_date.hour as u64 * SECONDS_IN_HOUR 323 | + days * SECONDS_IN_DAY, 324 | ) 325 | } 326 | } 327 | 328 | impl FromStr for HttpDate { 329 | type Err = http_types::Error; 330 | 331 | fn from_str(s: &str) -> Result { 332 | ensure!(s.is_ascii(), "String slice is not valid ASCII"); 333 | let x = s.trim().as_bytes(); 334 | let date = parse_imf_fixdate(x) 335 | .or_else(|_| parse_rfc850_date(x)) 336 | .or_else(|_| parse_asctime(x))?; 337 | ensure!(date.is_valid(), "Invalid date time"); 338 | Ok(date) 339 | } 340 | } 341 | 342 | impl Display for HttpDate { 343 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 344 | let week_day = match self.week_day { 345 | 1 => b"Mon", 346 | 2 => b"Tue", 347 | 3 => b"Wed", 348 | 4 => b"Thu", 349 | 5 => b"Fri", 350 | 6 => b"Sat", 351 | 7 => b"Sun", 352 | _ => unreachable!(), 353 | }; 354 | let month = match self.month { 355 | 1 => b"Jan", 356 | 2 => b"Feb", 357 | 3 => b"Mar", 358 | 4 => b"Apr", 359 | 5 => b"May", 360 | 6 => b"Jun", 361 | 7 => b"Jul", 362 | 8 => b"Aug", 363 | 9 => b"Sep", 364 | 10 => b"Oct", 365 | 11 => b"Nov", 366 | 12 => b"Dec", 367 | _ => unreachable!(), 368 | }; 369 | let mut buf: [u8; 29] = [ 370 | // Too long to write as: b"Thu, 01 Jan 1970 00:00:00 GMT" 371 | b' ', b' ', b' ', b',', b' ', b'0', b'0', b' ', b' ', b' ', b' ', b' ', b'0', b'0', 372 | b'0', b'0', b' ', b'0', b'0', b':', b'0', b'0', b':', b'0', b'0', b' ', b'G', b'M', 373 | b'T', 374 | ]; 375 | buf[0] = week_day[0]; 376 | buf[1] = week_day[1]; 377 | buf[2] = week_day[2]; 378 | buf[5] = b'0' + (self.day / 10); 379 | buf[6] = b'0' + (self.day % 10); 380 | buf[8] = month[0]; 381 | buf[9] = month[1]; 382 | buf[10] = month[2]; 383 | buf[12] = b'0' + (self.year / 1000) as u8; 384 | buf[13] = b'0' + (self.year / 100 % 10) as u8; 385 | buf[14] = b'0' + (self.year / 10 % 10) as u8; 386 | buf[15] = b'0' + (self.year % 10) as u8; 387 | buf[17] = b'0' + (self.hour / 10); 388 | buf[18] = b'0' + (self.hour % 10); 389 | buf[20] = b'0' + (self.minute / 10); 390 | buf[21] = b'0' + (self.minute % 10); 391 | buf[23] = b'0' + (self.second / 10); 392 | buf[24] = b'0' + (self.second % 10); 393 | f.write_str(from_utf8(&buf[..]).unwrap()) 394 | } 395 | } 396 | 397 | impl PartialEq for HttpDate { 398 | fn eq(&self, other: &HttpDate) -> bool { 399 | SystemTime::from(*self) == SystemTime::from(*other) 400 | } 401 | } 402 | 403 | impl PartialOrd for HttpDate { 404 | fn partial_cmp(&self, other: &HttpDate) -> Option { 405 | SystemTime::from(*self).partial_cmp(&SystemTime::from(*other)) 406 | } 407 | } 408 | 409 | fn is_leap_year(year: u16) -> bool { 410 | year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) 411 | } 412 | 413 | #[cfg(test)] 414 | mod tests { 415 | use std::time::{Duration, UNIX_EPOCH}; 416 | 417 | use super::{fmt_http_date, parse_http_date, HttpDate, SECONDS_IN_DAY, SECONDS_IN_HOUR}; 418 | 419 | #[test] 420 | fn test_rfc_example() { 421 | let d = UNIX_EPOCH + Duration::from_secs(784111777); 422 | assert_eq!( 423 | d, 424 | parse_http_date("Sun, 06 Nov 1994 08:49:37 GMT").expect("#1") 425 | ); 426 | assert_eq!( 427 | d, 428 | parse_http_date("Sunday, 06-Nov-94 08:49:37 GMT").expect("#2") 429 | ); 430 | assert_eq!(d, parse_http_date("Sun Nov 6 08:49:37 1994").expect("#3")); 431 | } 432 | 433 | #[test] 434 | fn test2() { 435 | let d = UNIX_EPOCH + Duration::from_secs(1475419451); 436 | assert_eq!( 437 | d, 438 | parse_http_date("Sun, 02 Oct 2016 14:44:11 GMT").expect("#1") 439 | ); 440 | assert!(parse_http_date("Sun Nov 10 08:00:00 1000").is_err()); 441 | assert!(parse_http_date("Sun Nov 10 08*00:00 2000").is_err()); 442 | assert!(parse_http_date("Sunday, 06-Nov-94 08+49:37 GMT").is_err()); 443 | } 444 | 445 | #[test] 446 | fn test3() { 447 | let mut d = UNIX_EPOCH; 448 | assert_eq!(d, parse_http_date("Thu, 01 Jan 1970 00:00:00 GMT").unwrap()); 449 | d += Duration::from_secs(SECONDS_IN_HOUR); 450 | assert_eq!(d, parse_http_date("Thu, 01 Jan 1970 01:00:00 GMT").unwrap()); 451 | d += Duration::from_secs(SECONDS_IN_DAY); 452 | assert_eq!(d, parse_http_date("Fri, 02 Jan 1970 01:00:00 GMT").unwrap()); 453 | d += Duration::from_secs(2592000); 454 | assert_eq!(d, parse_http_date("Sun, 01 Feb 1970 01:00:00 GMT").unwrap()); 455 | d += Duration::from_secs(2592000); 456 | assert_eq!(d, parse_http_date("Tue, 03 Mar 1970 01:00:00 GMT").unwrap()); 457 | d += Duration::from_secs(31536005); 458 | assert_eq!(d, parse_http_date("Wed, 03 Mar 1971 01:00:05 GMT").unwrap()); 459 | d += Duration::from_secs(15552000); 460 | assert_eq!(d, parse_http_date("Mon, 30 Aug 1971 01:00:05 GMT").unwrap()); 461 | d += Duration::from_secs(6048000); 462 | assert_eq!(d, parse_http_date("Mon, 08 Nov 1971 01:00:05 GMT").unwrap()); 463 | d += Duration::from_secs(864000000); 464 | assert_eq!(d, parse_http_date("Fri, 26 Mar 1999 01:00:05 GMT").unwrap()); 465 | } 466 | 467 | #[test] 468 | fn test_fmt() { 469 | let d = UNIX_EPOCH; 470 | assert_eq!(fmt_http_date(d), "Thu, 01 Jan 1970 00:00:00 GMT"); 471 | let d = UNIX_EPOCH + Duration::from_secs(1475419451); 472 | assert_eq!(fmt_http_date(d), "Sun, 02 Oct 2016 14:44:11 GMT"); 473 | } 474 | 475 | #[test] 476 | fn size_of() { 477 | assert_eq!(::std::mem::size_of::(), 8); 478 | } 479 | } 480 | --------------------------------------------------------------------------------