sagebind / isahc

The practical HTTP client that is fun to use.
https://docs.rs/isahc
MIT License
707 stars 62 forks source link

HEAD request with body errors if server does not also return a body #342

Open sagebind opened 3 years ago

sagebind commented 3 years ago

When constructing a boutique HTTP HEAD request with a non-empty body, Isahc will assume that the server will also return a body in kind. This breaks when the server returns a response with a Content-Length header, which is totally legal, but then Isahc tries to read a response payload of that size and fails.

Isahc should not be assuming that the server is going to break the HTTP spec just because the user making the request is! According to RFC 7230, Section 3.3 HEAD requests should never return a response body regardless of what headers are given. We should probably return Body::empty() for all responses to HEAD requests even if the server does include some garbage data after the response headers. Though it might differ exactly depending on whether HTTP/2 is in use or not since that has explicit frames for body data.

Here is a sample program demonstrating this issue (reproduced on Isahc 1.5.0, but also reproducible on 0.9.14 and likely older):

use std::io::Cursor;

use isahc::{Body, Request};

fn main() -> Result<(), isahc::Error> {
    let weird_request = Request::head("http://download.opensuse.org/update/tumbleweed/repodata/repomd.xml")
        .body(Body::from_reader(Cursor::new(b"")))?;

    let error = isahc::send(weird_request).unwrap_err();
    eprintln!("{:#?}", error);

    Ok(())
}

This program prints the following sexy error:

Error {
    kind: Io,
    context: None,
    source: Some(
        Error {
            description: "Transferred a partial file",
            code: 18,
            extra: None,
        },
    ),
    source_type: Some(
        "curl::error::Error",
    ),
    local_addr: Some(
        <redacted>:56556,
    ),
    remote_addr: Some(
        195.135.221.134:80,
    ),
}

Though on 0.9.14 it apparently produces a much more confusing and ambiguous error:

ResponseBodyError(None): unknown error