cloudflare / pingora

A library for building fast, reliable and evolvable network services.
Apache License 2.0
20.21k stars 1.1k forks source link

Gateway example forwards requests with multiple `Content-Length` headers #309

Open kenballus opened 3 days ago

kenballus commented 3 days ago

Describe the bug

The gateway example forwards requests with multiple Content-Length headers. The HTTP RFCs say not to forward such messages.

From RFC 7230:

If a message is received without Transfer-Encoding and with either multiple Content-Length header fields having differing field-values or a single Content-Length header field having an invalid value, then the message framing is invalid and the recipient MUST treat it as an unrecoverable error. If this is a request message, the server MUST respond with a 400 (Bad Request) status code and then close the connection.

Thus, upon receipt of a request with multiple Content-Length headers, the gateway example should reject the request with a 400.

Pingora info

Please include the following information about your environment:

Pingora version: 648a6ad0554442be5f9c8d396a66843552156d1f Rust version: cargo 1.79.0 (ffa9cf99a 2024-06-03) Operating system version: Debian 13

Steps to reproduce

  1. Get a fresh Debian container:
    docker run --rm -it debian:trixie-slim
  2. Install dependencies:
    apt -y update && apt -y upgrade && apt -y install ncat clang git curl cmake g++ && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && . ~/.cargo/env
  3. Clone and build Pingora:
    git clone 'https://github.com/cloudflare/pingora' && cd pingora && cargo build --example gateway
  4. Run the gateway:
    ./target/debug/examples/gateway &
  5. Send a control request, and observe that it works as expected:
    (printf 'GET / HTTP/1.1\r\nContent-Length: 1\r\nHost: one.one.one.one\r\n\r\nZ'; sleep 1) | ncat localhost 6191
  6. Add an extra Content-Length header, and send it again:
    (printf 'GET / HTTP/1.1\r\nContent-Length: 1\r\nContent-Length: 0\r\nHost: one.one.one.one\r\n\r\nZ'; sleep 1) | ncat localhost 6191

Expected results

Pingora should respond 400.

Observed results

Pingora forwarded the request to one.one.one.one, which responds 400. This is clear because the response has a CF-RAY header, which Pingora wouldn't insert on its own.

eaufavor commented 1 day ago

More reference https://datatracker.ietf.org/doc/html/rfc9112#section-6.3