Skip to content

llhttp allows empty header names #257

@kenballus

Description

@kenballus

The RFCs require that header names be nonempty, but llhttp doesn't enforce this. I think it's probably worth enforcing this (at least when the leniency flags are disabled) because empty header names have caused issues for other servers in the past. For example, both Mongoose and HAProxy have had issues with treating \r\n:\r\n as equivalent to \r\n\r\n for termination of the header block.

Relevant grammar rules from RFC 9110

field-name = token
token = 1*tchar
tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA

(i.e. a header field name must consist of 1 or more tchars)

Instructions to reproduce

  1. Clone this repo and build.
  2. Stick the following program in the root of the repo as main.c: (this is just the example program from the README with a payload containing an empty header name)
#include <stdio.h>
#include <string.h>
#include "./build/llhttp.h"

int handle_on_message_complete(llhttp_t* parser) {
  fprintf(stdout, "Message completed!\n");
  return 0;
}

int main() {
  llhttp_t parser;
  llhttp_settings_t settings;

  /*Initialize user callbacks and settings */
  llhttp_settings_init(&settings);

  /*Set user callback */
  settings.on_message_complete = handle_on_message_complete;

  /*Initialize the parser in HTTP_BOTH mode, meaning that it will select between
  *HTTP_REQUEST and HTTP_RESPONSE parsing automatically while reading the first
  *input.
  */
  llhttp_init(&parser, HTTP_BOTH, &settings);

  /*Parse request! */
  const char* request = "GET / HTTP/1.1\r\n: this-should-not-work\r\n\r\n";
  int request_len = strlen(request);

  enum llhttp_errno err = llhttp_execute(&parser, request, request_len);
  if (err == HPE_OK) {
    fprintf(stdout, "Successfully parsed!\n");
  } else {
    fprintf(stderr, "Parse error: %s %s\n", llhttp_errno_name(err), parser.reason);
  }
}
  1. Compile main.c:
gcc main.c build/libllhttp.a
  1. Run the binary and observe that it parses when it should reject:
./a.out
Message completed!
Successfully parsed!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions