seanmonstar / httparse

A push parser for the HTTP 1.x protocol in Rust.
https://docs.rs/httparse
Apache License 2.0
573 stars 113 forks source link

invalid HTTP status-code parsed #55

Closed surajprak closed 2 years ago

surajprak commented 5 years ago

Here is my hyper trace

TRACE hyper::client::pool > checkout waiting for idle connection: "http://10.200.14.75:8000" TRACE hyper::client::connect::http > Http::connect; scheme=http, host=10.200.14.75, port=Some(8000) DEBUG hyper::client::connect::http > connecting to 10.200.14.75:8000 DEBUG hyper::client::connect::http > connected to Some(V4(10.200.14.75:8000)) TRACE hyper::client::conn > client handshake HTTP/1 TRACE hyper::client > handshake complete, spawning background dispatcher task TRACE hyper::proto::h1::conn > flushed({role=client}): State { reading: Init, writing: Init, keep_alive: Busy } TRACE hyper::client::pool > checkout dropped for "http://10.200.14.75:8000" TRACE hyper::proto::h1::role > Client::encode method=POST, body=Some(Known(6)) DEBUG hyper::proto::h1::io > flushed 152 bytes TRACE hyper::proto::h1::conn > flushed({role=client}): State { reading: Init, writing: KeepAlive, keep_alive: Busy } TRACE hyper::proto::h1::conn > Conn::read_head DEBUG hyper::proto::h1::io > read 172 bytes TRACE hyper::proto::h1::role > Response.parse([Header; 100], [u8; 172]) TRACE hyper::proto::h1::conn > State::close_read() DEBUG hyper::proto::h1::conn > parse error (invalid HTTP status-code parsed) with 172 bytes DEBUG hyper::proto::h1::dispatch > read_head error: invalid HTTP status-code parsed Error invalid HTTP status-code parsed TRACE hyper::proto::h1::conn > State::close() TRACE hyper::proto::h1::conn > flushed({role=client}): State { reading: Closed, writing: Closed, keep_alive: Disabled } TRACE hyper::proto::h1::conn > shut down IO complete

Same call works fine with curl cmd

curl -d 'abcdef1234abcdef=27062019112303|' http://10.200.14.75:8000 -v

Any idea what is wrong here. My rust code is

extern crate hyper;
extern crate pretty_env_logger;

use std::io::{self, Write};

use hyper::{Client, Request, Body};
use hyper::rt::{self, Future, Stream};

fn main() {
    pretty_env_logger::init();

    let url = "http://10.200.14.75:8000".to_string();

    let url = url.parse::<hyper::Uri>().unwrap();
    if url.scheme_part().map(|s| s.as_ref()) != Some("http") {
        println!("This example only works with 'http' URLs.");
        return;
    }

    rt::run(fetch_url(url));
}

fn fetch_url(url: hyper::Uri) -> impl Future<Item=(), Error=()> {
    let mut request = Request::builder();
    let req = request
       .method("POST")
       .uri("http://10.200.14.75:8000")
       .header("User-Agent", "my-awesome-agent/1.0")
       .header("Content-Type", "application/x-www-form-urlencoded").body(Body::from("Hallo!"))
    .expect("request builder");

    let client = Client::builder()
    .keep_alive(true)
    .build_http();
    client
        .request(req)
        .and_then(|res| {
            println!("Response: {}", res.status());
            println!("Headers: {:#?}", res.headers());

            res.into_body().for_each(|chunk| {
                io::stdout().write_all(&chunk)
                    .map_err(|e| panic!("example expects stdout is open, error={}", e))
            })
        })
        .map(|_| {
            println!("\n\nDone.");
        })
        .map_err(|err| {
            eprintln!("Error {}", err);
        })
}
surajprak commented 5 years ago

Here is the hex dump of response

48, 54, 54, 50, 2F, 31, 2E, 31, 20, 32, 30, 30, 20, 4F, 4B, 0, D, A, 53, 65, 72, 76, 65, 72, 3A, 20, 4D, 65, 73, 73, 79, D, A, 44, 61, 74, 65, 3A, 20, 46, 72, 69, 2C, 20, 33, 31, 20, 41, 75, 67, 20, 32, 30, 31, 31, 20, 30, 30, 3A, 33, 31, 3A, 35, 33, 20, 47, 4D, 54, D, A, 43, 6F, 6E, 74, 65, 6E, 74, 2D, 54, 79, 70, 65, 3A, 20, 74, 65, 78, 74, 2F, 68, 74, 6D, 6C, D, A, 43, 6F, 6E, 6E, 65, 63, 74, 69, 6F, 6E, 3A, 20, 4B, 65, 65, 70, 2D, 41, 6C, 69, 76, 65, D, A, 43, 6F, 6E, 74, 65, 6E, 74, 2D, 4C, 65, 6E, 67, 74, 68, 3A, 20, 31, 35, D, A, D, A, 32, 30, 31, 39, 36, 39, 38, 34, 38, 35, 38, 30, D, A, 0

On further investigation, in httparse library

/// From [RFC 7230](https://tools.ietf.org/html/rfc7230):
///
/// > ```notrust
/// > reason-phrase  = *( HTAB / SP / VCHAR / obs-text )
/// > HTAB           = %x09        ; horizontal tab
/// > VCHAR          = %x21-7E     ; visible (printing) characters
/// > obs-text       = %x80-FF
/// > ```
///
/// > A.2.  Changes from RFC 2616
/// >
/// > Non-US-ASCII content in header fields and the reason phrase
/// > has been obsoleted and made opaque (the TEXT rule was removed).
///
/// Note that the following implementation deliberately rejects the obsoleted (non-US-ASCII) text range.
///
/// The fully compliant parser should probably just return the reason-phrase as an opaque &[u8] data
/// and leave interpretation to user or specialized helpers (akin to .display() in std::path::Path)
#[inline]
fn parse_reason<'a>(bytes: &mut Bytes<'a>) -> Result<&'a str> {
    loop {
        let b = next!(bytes);
        if b == b'\r' {
            expect!(bytes.next() == b'\n' => Err(Error::Status));
            return Ok(Status::Complete(unsafe {
                // all bytes up till `i` must have been HTAB / SP / VCHAR
                str::from_utf8_unchecked(bytes.slice_skip(2))
            }));
        } else if b == b'\n' {
            return Ok(Status::Complete(unsafe {
                // all bytes up till `i` must have been HTAB / SP / VCHAR
                str::from_utf8_unchecked(bytes.slice_skip(1))
            }));
        } **else if !((b >= 0x20 && b <= 0x7E) || b == b'\t') {
            return Err(Error::Status);
        }**
    }
}

Since, we have a 00 in request 48, 54, 54, 50, 2F, 31, 2E, 31, 20, 32, 30, 30, 20, 4F, 4B, 0, D, A else if !((b >= 0x20 && b <= 0x7E) || b == b'\t') check is true and its returning as error. Shall we allow 0 (null) char as a valid one?

Other libraries like python requests, curl is allowing it.

seanmonstar commented 5 years ago

Hrm, even if we weren't as strict and supported obs-text, that would still exclude byte 0... What servers put a null byte in the status code??

surajprak commented 5 years ago

Ha ha, its actually coming from the server of a very big international bank and most likely its C-string which they are trying to concat. It will be a time taking exercise to convince them of changing their server. Moreover, they will say that parser in other languages are supporting it. Is their a work around for this?

nox commented 2 years ago

Chrome rejects NUL anywhere in a response head, let's close this.