Skip to content

Transfer-Encoding with chunked as the last coding is refused with 400 since v20.70.0 #1941

Description

@nigrosimone

Since v20.70.0 the new check in HttpParser.h compares the whole Transfer-Encoding value against the literal string "chunked":

if (transferEncodingString != "chunked") {

    /* We only support chunked */
    if (transferEncodingString != "chunked") {
        return {HTTP_ERROR_400_BAD_REQUEST, FULLPTR};
    }

Two problems with it:

  1. Transfer-Encoding: CHUNKED is refused. RFC 9112 section 7 says "All transfer-coding names are case-insensitive", so this one has to be accepted.
  2. gzip, chunked gets 400. Refusing it is fine, uWS does not implement gzip as a transfer coding, but RFC 9112 section 6.1 asks for 501 (Not Implemented) here, not 400. A client now reads "your request is malformed" when the real answer is "I do not support this".

v20.69.0 answered 200 to both, and so does node's own http server, so this is also a behaviour change for anyone upgrading.

Repro below, prints one line per case.

// Transfer-Encoding handling, uWS 20.69.0 vs 20.70.0.
const uWS = require("uWebSockets.js");
const net = require("net");

const CASES = ["chunked", "CHUNKED", "gzip, chunked", "deflate, gzip, chunked", "identity, chunked"];

function ask(port, te) {
    return new Promise((resolve) => {
        const s = net.connect(port, "127.0.0.1", () =>
            s.write(`POST / HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: ${te}\r\n\r\n5\r\nhello\r\n0\r\n\r\n`)
        );
        let buf = "";
        s.on("data", (d) => (buf += d));
        s.on("error", () => resolve("connection error"));
        s.on("close", () => resolve(buf.split("\r\n")[0] || "closed with no answer"));
        setTimeout(() => { s.destroy(); resolve(buf.split("\r\n")[0] || "no answer"); }, 800);
    });
}

uWS.App()
    .any("/*", (res) => {
        res.onAborted(() => {});
        res.onData(() => {});
        res.end("ok");
    })
    .listen("127.0.0.1", 0, async (token) => {
        const port = uWS.us_socket_local_port(token);
        for (const te of CASES) {
            console.log(JSON.stringify(te).padEnd(26) + (await ask(port, te)));
        }
        uWS.us_listen_socket_close(token);
    });
                          20.69.0            20.70.0
"chunked"                 200 OK             200 OK
"CHUNKED"                 200 OK             400 Bad Request
"gzip, chunked"           200 OK             400 Bad Request
"deflate, gzip, chunked"  200 OK             400 Bad Request
"identity, chunked"       200 OK             400 Bad Request

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions