Update HTTP parser to LLHTTP (#2263)

Motivation:

The node.js HTTP parser library that we use has been unmaintained for some time. We should move to the maintained replacement, which is llhttp. This patch will update our dependency and bring us over to the new library, as well as make any changes we need.

Modifications:

This patch comes in 4 parts, each contained in a separate commit in the PR.

The first commit drops the existing http_parser code and updates some of the repo state for using llhttp.
The second commit rewrites the update script to bring in llhttp instead of http_parser.
The third runs the actual script. You can skip reviewing this except to sanity check the outcome.
The fourth commit updates the NIO code and the tests to get everything working.

In general the substance of the product modifications was minimal. The logic around keeping track of where we are in the buffer and how upgrades work has changed a bit, so that required some fiddling. I also had to add an error reporting path for the delegates to be able to throw specific errors that llhttp no longer checks for. Finally, I removed two tests that were a little overzealous and that llhttp does not police.

Result:

Back on the supported path.
This commit is contained in:
Cory Benfield 2022-09-13 15:09:57 +02:00 committed by GitHub
parent f16991836d
commit 6918034260
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 17910 additions and 3398 deletions

View File

@ -45,12 +45,12 @@ This product contains a derivation of the Tony Stone's 'process_test_files.rb'.
---
This product contains NodeJS's http-parser.
This product contains NodeJS's llhttp.
* LICENSE (MIT):
* https://github.com/nodejs/http-parser/blob/master/LICENSE-MIT
* https://github.com/nodejs/llhttp/blob/master/LICENSE-MIT
* HOMEPAGE:
* https://github.com/nodejs/http-parser
* https://github.com/nodejs/llhttp
---

View File

@ -52,7 +52,7 @@ var targets: [PackageDescription.Target] = [
.target(name: "NIOConcurrencyHelpers",
dependencies: ["CNIOAtomics"]),
.target(name: "NIOHTTP1",
dependencies: ["NIO", "NIOCore", "NIOConcurrencyHelpers", "CNIOHTTPParser"]),
dependencies: ["NIO", "NIOCore", "NIOConcurrencyHelpers", "CNIOLLHTTP"]),
.executableTarget(name: "NIOEchoServer",
dependencies: ["NIOPosix", "NIOCore", "NIOConcurrencyHelpers"],
exclude: ["README.md"]),
@ -65,7 +65,7 @@ var targets: [PackageDescription.Target] = [
.executableTarget(name: "NIOHTTP1Client",
dependencies: ["NIOPosix", "NIOCore", "NIOHTTP1", "NIOConcurrencyHelpers"],
exclude: ["README.md"]),
.target(name: "CNIOHTTPParser"),
.target(name: "CNIOLLHTTP"),
.target(name: "NIOTLS", dependencies: ["NIO", "NIOCore"]),
.executableTarget(name: "NIOChatServer",
dependencies: ["NIOPosix", "NIOCore", "NIOConcurrencyHelpers"],

View File

@ -52,7 +52,7 @@ var targets: [PackageDescription.Target] = [
.target(name: "NIOConcurrencyHelpers",
dependencies: ["CNIOAtomics"]),
.target(name: "NIOHTTP1",
dependencies: ["NIO", "NIOCore", "NIOConcurrencyHelpers", "CNIOHTTPParser"]),
dependencies: ["NIO", "NIOCore", "NIOConcurrencyHelpers", "CNIOLLHTTP"]),
.executableTarget(name: "NIOEchoServer",
dependencies: ["NIOPosix", "NIOCore", "NIOConcurrencyHelpers"],
exclude: ["README.md"]),
@ -65,7 +65,7 @@ var targets: [PackageDescription.Target] = [
.executableTarget(name: "NIOHTTP1Client",
dependencies: ["NIOPosix", "NIOCore", "NIOHTTP1", "NIOConcurrencyHelpers"],
exclude: ["README.md"]),
.target(name: "CNIOHTTPParser"),
.target(name: "CNIOLLHTTP"),
.target(name: "NIOTLS", dependencies: ["NIO", "NIOCore"]),
.executableTarget(name: "NIOChatServer",
dependencies: ["NIOPosix", "NIOCore", "NIOConcurrencyHelpers"],

View File

@ -52,7 +52,7 @@ var targets: [PackageDescription.Target] = [
.target(name: "NIOConcurrencyHelpers",
dependencies: ["CNIOAtomics"]),
.target(name: "NIOHTTP1",
dependencies: ["NIO", "NIOCore", "NIOConcurrencyHelpers", "CNIOHTTPParser"]),
dependencies: ["NIO", "NIOCore", "NIOConcurrencyHelpers", "CNIOLLHTTP"]),
.executableTarget(name: "NIOEchoServer",
dependencies: ["NIOPosix", "NIOCore", "NIOConcurrencyHelpers"],
exclude: ["README.md"]),
@ -65,7 +65,7 @@ var targets: [PackageDescription.Target] = [
.executableTarget(name: "NIOHTTP1Client",
dependencies: ["NIOPosix", "NIOCore", "NIOHTTP1", "NIOConcurrencyHelpers"],
exclude: ["README.md"]),
.target(name: "CNIOHTTPParser"),
.target(name: "CNIOLLHTTP"),
.target(name: "NIOTLS", dependencies: ["NIO", "NIOCore"]),
.executableTarget(name: "NIOChatServer",
dependencies: ["NIOPosix", "NIOCore", "NIOConcurrencyHelpers"],

View File

@ -1,68 +0,0 @@
# Authors ordered by first contribution.
Ryan Dahl <ry@tinyclouds.org>
Jeremy Hinegardner <jeremy@hinegardner.org>
Sergey Shepelev <temotor@gmail.com>
Joe Damato <ice799@gmail.com>
tomika <tomika_nospam@freemail.hu>
Phoenix Sol <phoenix@burninglabs.com>
Cliff Frey <cliff@meraki.com>
Ewen Cheslack-Postava <ewencp@cs.stanford.edu>
Santiago Gala <sgala@apache.org>
Tim Becker <tim.becker@syngenio.de>
Jeff Terrace <jterrace@gmail.com>
Ben Noordhuis <info@bnoordhuis.nl>
Nathan Rajlich <nathan@tootallnate.net>
Mark Nottingham <mnot@mnot.net>
Aman Gupta <aman@tmm1.net>
Tim Becker <tim.becker@kuriositaet.de>
Sean Cunningham <sean.cunningham@mandiant.com>
Peter Griess <pg@std.in>
Salman Haq <salman.haq@asti-usa.com>
Cliff Frey <clifffrey@gmail.com>
Jon Kolb <jon@b0g.us>
Fouad Mardini <f.mardini@gmail.com>
Paul Querna <pquerna@apache.org>
Felix Geisendörfer <felix@debuggable.com>
koichik <koichik@improvement.jp>
Andre Caron <andre.l.caron@gmail.com>
Ivo Raisr <ivosh@ivosh.net>
James McLaughlin <jamie@lacewing-project.org>
David Gwynne <loki@animata.net>
Thomas LE ROUX <thomas@november-eleven.fr>
Randy Rizun <rrizun@ortivawireless.com>
Andre Louis Caron <andre.louis.caron@usherbrooke.ca>
Simon Zimmermann <simonz05@gmail.com>
Erik Dubbelboer <erik@dubbelboer.com>
Martell Malone <martellmalone@gmail.com>
Bertrand Paquet <bpaquet@octo.com>
BogDan Vatra <bogdan@kde.org>
Peter Faiman <peter@thepicard.org>
Corey Richardson <corey@octayn.net>
Tóth Tamás <tomika_nospam@freemail.hu>
Cam Swords <cam.swords@gmail.com>
Chris Dickinson <christopher.s.dickinson@gmail.com>
Uli Köhler <ukoehler@btronik.de>
Charlie Somerville <charlie@charliesomerville.com>
Patrik Stutz <patrik.stutz@gmail.com>
Fedor Indutny <fedor.indutny@gmail.com>
runner <runner.mei@gmail.com>
Alexis Campailla <alexis@janeasystems.com>
David Wragg <david@wragg.org>
Vinnie Falco <vinnie.falco@gmail.com>
Alex Butum <alexbutum@linux.com>
Rex Feng <rexfeng@gmail.com>
Alex Kocharin <alex@kocharin.ru>
Mark Koopman <markmontymark@yahoo.com>
Helge Heß <me@helgehess.eu>
Alexis La Goutte <alexis.lagoutte@gmail.com>
George Miroshnykov <george.miroshnykov@gmail.com>
Maciej Małecki <me@mmalecki.com>
Marc O'Morain <github.com@marcomorain.com>
Jeff Pinner <jpinner@twitter.com>
Timothy J Fontaine <tjfontaine@gmail.com>
Akagi201 <akagi201@gmail.com>
Romain Giraud <giraud.romain@gmail.com>
Jay Satiro <raysatiro@yahoo.com>
Arne Steen <Arne.Steen@gmx.de>
Kjell Schubert <kjell.schubert@gmail.com>
Olivier Mengué <dolmen@cpan.org>

View File

@ -1,19 +0,0 @@
Copyright Joyent, Inc. and other Node contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
// adaptions for http_parser to make it more straightforward to use from Swift
#ifndef C_NIO_HTTP_PARSER_SWIFT
#define C_NIO_HTTP_PARSER_SWIFT
#include "c_nio_http_parser.h"
static inline size_t c_nio_http_parser_execute_swift(http_parser *parser,
const http_parser_settings *settings,
const void *data,
size_t len) {
return c_nio_http_parser_execute(parser, settings, (const char *)data, len);
}
#endif

View File

@ -1,452 +0,0 @@
/* Additional changes for SwiftNIO:
- prefixed all symbols by 'c_nio_'
*/
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef http_parser_h
#define http_parser_h
#ifdef __cplusplus
extern "C" {
#endif
/* Also update SONAME in the Makefile whenever you change these. */
#define HTTP_PARSER_VERSION_MAJOR 2
#define HTTP_PARSER_VERSION_MINOR 9
#define HTTP_PARSER_VERSION_PATCH 4
#include <stddef.h>
#if defined(_WIN32) && !defined(__MINGW32__) && \
(!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
#include <BaseTsd.h>
typedef __int8 int8_t;
typedef unsigned __int8 uint8_t;
typedef __int16 int16_t;
typedef unsigned __int16 uint16_t;
typedef __int32 int32_t;
typedef unsigned __int32 uint32_t;
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
#elif (defined(__sun) || defined(__sun__)) && defined(__SunOS_5_9)
#include <sys/inttypes.h>
#else
#include <stdint.h>
#endif
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
* faster
*/
#ifndef HTTP_PARSER_STRICT
# define HTTP_PARSER_STRICT 1
#endif
/* Maximium header size allowed. If the macro is not defined
* before including this header then the default is used. To
* change the maximum header size, define the macro in the build
* environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove
* the effective limit on the size of the header, define the macro
* to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)
*/
#ifndef HTTP_MAX_HEADER_SIZE
# define HTTP_MAX_HEADER_SIZE (80*1024)
#endif
typedef struct http_parser http_parser;
typedef struct http_parser_settings http_parser_settings;
/* Callbacks should return non-zero to indicate an error. The parser will
* then halt execution.
*
* The one exception is on_headers_complete. In a HTTP_RESPONSE parser
* returning '1' from on_headers_complete will tell the parser that it
* should not expect a body. This is used when receiving a response to a
* HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
* chunked' headers that indicate the presence of a body.
*
* Returning `2` from on_headers_complete will tell parser that it should not
* expect neither a body nor any futher responses on this connection. This is
* useful for handling responses to a CONNECT request which may not contain
* `Upgrade` or `Connection: upgrade` headers.
*
* http_data_cb does not return data chunks. It will be called arbitrarily
* many times for each string. E.G. you might get 10 callbacks for "on_url"
* each providing just a few characters more data.
*/
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
typedef int (*http_cb) (http_parser*);
/* Status Codes */
#define HTTP_STATUS_MAP(XX) \
XX(100, CONTINUE, Continue) \
XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \
XX(102, PROCESSING, Processing) \
XX(200, OK, OK) \
XX(201, CREATED, Created) \
XX(202, ACCEPTED, Accepted) \
XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \
XX(204, NO_CONTENT, No Content) \
XX(205, RESET_CONTENT, Reset Content) \
XX(206, PARTIAL_CONTENT, Partial Content) \
XX(207, MULTI_STATUS, Multi-Status) \
XX(208, ALREADY_REPORTED, Already Reported) \
XX(226, IM_USED, IM Used) \
XX(300, MULTIPLE_CHOICES, Multiple Choices) \
XX(301, MOVED_PERMANENTLY, Moved Permanently) \
XX(302, FOUND, Found) \
XX(303, SEE_OTHER, See Other) \
XX(304, NOT_MODIFIED, Not Modified) \
XX(305, USE_PROXY, Use Proxy) \
XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \
XX(308, PERMANENT_REDIRECT, Permanent Redirect) \
XX(400, BAD_REQUEST, Bad Request) \
XX(401, UNAUTHORIZED, Unauthorized) \
XX(402, PAYMENT_REQUIRED, Payment Required) \
XX(403, FORBIDDEN, Forbidden) \
XX(404, NOT_FOUND, Not Found) \
XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \
XX(406, NOT_ACCEPTABLE, Not Acceptable) \
XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \
XX(408, REQUEST_TIMEOUT, Request Timeout) \
XX(409, CONFLICT, Conflict) \
XX(410, GONE, Gone) \
XX(411, LENGTH_REQUIRED, Length Required) \
XX(412, PRECONDITION_FAILED, Precondition Failed) \
XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \
XX(414, URI_TOO_LONG, URI Too Long) \
XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \
XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \
XX(417, EXPECTATION_FAILED, Expectation Failed) \
XX(421, MISDIRECTED_REQUEST, Misdirected Request) \
XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \
XX(423, LOCKED, Locked) \
XX(424, FAILED_DEPENDENCY, Failed Dependency) \
XX(426, UPGRADE_REQUIRED, Upgrade Required) \
XX(428, PRECONDITION_REQUIRED, Precondition Required) \
XX(429, TOO_MANY_REQUESTS, Too Many Requests) \
XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \
XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \
XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \
XX(501, NOT_IMPLEMENTED, Not Implemented) \
XX(502, BAD_GATEWAY, Bad Gateway) \
XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \
XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \
XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \
XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \
XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \
XX(508, LOOP_DETECTED, Loop Detected) \
XX(510, NOT_EXTENDED, Not Extended) \
XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \
enum http_status
{
#define XX(num, name, string) HTTP_STATUS_##name = num,
HTTP_STATUS_MAP(XX)
#undef XX
};
/* Request Methods */
#define HTTP_METHOD_MAP(XX) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
XX(2, HEAD, HEAD) \
XX(3, POST, POST) \
XX(4, PUT, PUT) \
/* pathological */ \
XX(5, CONNECT, CONNECT) \
XX(6, OPTIONS, OPTIONS) \
XX(7, TRACE, TRACE) \
/* WebDAV */ \
XX(8, COPY, COPY) \
XX(9, LOCK, LOCK) \
XX(10, MKCOL, MKCOL) \
XX(11, MOVE, MOVE) \
XX(12, PROPFIND, PROPFIND) \
XX(13, PROPPATCH, PROPPATCH) \
XX(14, SEARCH, SEARCH) \
XX(15, UNLOCK, UNLOCK) \
XX(16, BIND, BIND) \
XX(17, REBIND, REBIND) \
XX(18, UNBIND, UNBIND) \
XX(19, ACL, ACL) \
/* subversion */ \
XX(20, REPORT, REPORT) \
XX(21, MKACTIVITY, MKACTIVITY) \
XX(22, CHECKOUT, CHECKOUT) \
XX(23, MERGE, MERGE) \
/* upnp */ \
XX(24, MSEARCH, M-SEARCH) \
XX(25, NOTIFY, NOTIFY) \
XX(26, SUBSCRIBE, SUBSCRIBE) \
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
/* RFC-5789 */ \
XX(28, PATCH, PATCH) \
XX(29, PURGE, PURGE) \
/* CalDAV */ \
XX(30, MKCALENDAR, MKCALENDAR) \
/* RFC-2068, section 19.6.1.2 */ \
XX(31, LINK, LINK) \
XX(32, UNLINK, UNLINK) \
/* icecast */ \
XX(33, SOURCE, SOURCE) \
enum http_method
{
#define XX(num, name, string) HTTP_##name = num,
HTTP_METHOD_MAP(XX)
#undef XX
};
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
/* Flag values for http_parser.flags field */
enum flags
{ F_CHUNKED = 1 << 0
, F_CONNECTION_KEEP_ALIVE = 1 << 1
, F_CONNECTION_CLOSE = 1 << 2
, F_CONNECTION_UPGRADE = 1 << 3
, F_TRAILING = 1 << 4
, F_UPGRADE = 1 << 5
, F_SKIPBODY = 1 << 6
, F_CONTENTLENGTH = 1 << 7
};
/* Map for errno-related constants
*
* The provided argument should be a macro that takes 2 arguments.
*/
#define HTTP_ERRNO_MAP(XX) \
/* No error */ \
XX(OK, "success") \
\
/* Callback-related errors */ \
XX(CB_message_begin, "the on_message_begin callback failed") \
XX(CB_url, "the on_url callback failed") \
XX(CB_header_field, "the on_header_field callback failed") \
XX(CB_header_value, "the on_header_value callback failed") \
XX(CB_headers_complete, "the on_headers_complete callback failed") \
XX(CB_body, "the on_body callback failed") \
XX(CB_message_complete, "the on_message_complete callback failed") \
XX(CB_status, "the on_status callback failed") \
XX(CB_chunk_header, "the on_chunk_header callback failed") \
XX(CB_chunk_complete, "the on_chunk_complete callback failed") \
\
/* Parsing-related errors */ \
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
XX(HEADER_OVERFLOW, \
"too many header bytes seen; overflow detected") \
XX(CLOSED_CONNECTION, \
"data received after completed connection: close message") \
XX(INVALID_VERSION, "invalid HTTP version") \
XX(INVALID_STATUS, "invalid HTTP status code") \
XX(INVALID_METHOD, "invalid HTTP method") \
XX(INVALID_URL, "invalid URL") \
XX(INVALID_HOST, "invalid host") \
XX(INVALID_PORT, "invalid port") \
XX(INVALID_PATH, "invalid path") \
XX(INVALID_QUERY_STRING, "invalid query string") \
XX(INVALID_FRAGMENT, "invalid fragment") \
XX(LF_EXPECTED, "LF character expected") \
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
XX(INVALID_CONTENT_LENGTH, \
"invalid character in content-length header") \
XX(UNEXPECTED_CONTENT_LENGTH, \
"unexpected content-length header") \
XX(INVALID_CHUNK_SIZE, \
"invalid character in chunk size header") \
XX(INVALID_CONSTANT, "invalid constant string") \
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
XX(STRICT, "strict mode assertion failed") \
XX(PAUSED, "parser is paused") \
XX(UNKNOWN, "an unknown error occurred") \
XX(INVALID_TRANSFER_ENCODING, \
"request has invalid transfer-encoding") \
/* Define HPE_* values for each errno value above */
#define HTTP_ERRNO_GEN(n, s) HPE_##n,
enum http_errno {
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
};
#undef HTTP_ERRNO_GEN
/* Get an http_errno value from an http_parser */
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
struct http_parser {
/** PRIVATE **/
unsigned int type : 2; /* enum http_parser_type */
unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */
unsigned int state : 7; /* enum state from http_parser.c */
unsigned int header_state : 7; /* enum header_state from http_parser.c */
unsigned int index : 5; /* index into current matcher */
unsigned int uses_transfer_encoding : 1; /* Transfer-Encoding header is present */
unsigned int allow_chunked_length : 1; /* Allow headers with both
* `Content-Length` and
* `Transfer-Encoding: chunked` set */
unsigned int lenient_http_headers : 1;
uint32_t nread; /* # bytes read in various scenarios */
uint64_t content_length; /* # bytes in body. `(uint64_t) -1` (all bits one)
* if no Content-Length header.
*/
/** READ-ONLY **/
unsigned short http_major;
unsigned short http_minor;
unsigned int status_code : 16; /* responses only */
unsigned int method : 8; /* requests only */
unsigned int http_errno : 7;
/* 1 = Upgrade header was present and the parser has exited because of that.
* 0 = No upgrade header present.
* Should be checked when c_nio_http_parser_execute() returns in addition to
* error checking.
*/
unsigned int upgrade : 1;
/** PUBLIC **/
void *data; /* A pointer to get hook to the "connection" or "socket" object */
};
struct http_parser_settings {
http_cb on_message_begin;
http_data_cb on_url;
http_data_cb on_status;
http_data_cb on_header_field;
http_data_cb on_header_value;
http_cb on_headers_complete;
http_data_cb on_body;
http_cb on_message_complete;
/* When on_chunk_header is called, the current chunk length is stored
* in parser->content_length.
*/
http_cb on_chunk_header;
http_cb on_chunk_complete;
};
enum http_parser_url_fields
{ UF_SCHEMA = 0
, UF_HOST = 1
, UF_PORT = 2
, UF_PATH = 3
, UF_QUERY = 4
, UF_FRAGMENT = 5
, UF_USERINFO = 6
, UF_MAX = 7
};
/* Result structure for c_nio_http_parser_parse_url().
*
* Callers should index into field_data[] with UF_* values iff field_set
* has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
* because we probably have padding left over), we convert any port to
* a uint16_t.
*/
struct http_parser_url {
uint16_t field_set; /* Bitmask of (1 << UF_*) values */
uint16_t port; /* Converted UF_PORT string */
struct {
uint16_t off; /* Offset into buffer in which field starts */
uint16_t len; /* Length of run in buffer */
} field_data[UF_MAX];
};
/* Returns the library version. Bits 16-23 contain the major version number,
* bits 8-15 the minor version number and bits 0-7 the patch level.
* Usage example:
*
* unsigned long version = c_nio_http_parser_version();
* unsigned major = (version >> 16) & 255;
* unsigned minor = (version >> 8) & 255;
* unsigned patch = version & 255;
* printf("http_parser v%u.%u.%u\n", major, minor, patch);
*/
unsigned long c_nio_http_parser_version(void);
void c_nio_http_parser_init(http_parser *parser, enum http_parser_type type);
/* Initialize http_parser_settings members to 0
*/
void c_nio_http_parser_settings_init(http_parser_settings *settings);
/* Executes the parser. Returns number of parsed bytes. Sets
* `parser->http_errno` on error. */
size_t c_nio_http_parser_execute(http_parser *parser,
const http_parser_settings *settings,
const char *data,
size_t len);
/* If c_nio_http_should_keep_alive() in the on_headers_complete or
* on_message_complete callback returns 0, then this should be
* the last message on the connection.
* If you are the server, respond with the "Connection: close" header.
* If you are the client, close the connection.
*/
int c_nio_http_should_keep_alive(const http_parser *parser);
/* Returns a string version of the HTTP method. */
const char *c_nio_http_method_str(enum http_method m);
/* Returns a string version of the HTTP status code. */
const char *c_nio_http_status_str(enum http_status s);
/* Return a string name of the given error */
const char *c_nio_http_errno_name(enum http_errno err);
/* Return a string description of the given error */
const char *c_nio_http_errno_description(enum http_errno err);
/* Initialize all http_parser_url members to 0 */
void c_nio_http_parser_url_init(struct http_parser_url *u);
/* Parse a URL; return nonzero on failure */
int c_nio_http_parser_parse_url(const char *buf, size_t buflen,
int is_connect,
struct http_parser_url *u);
/* Pause or un-pause the parser; a nonzero value pauses */
void c_nio_http_parser_pause(http_parser *parser, int paused);
/* Checks if this is the final chunk of the body. */
int c_nio_http_body_is_final(const http_parser *parser);
/* Change the maximum header size provided at compile time. */
void c_nio_http_parser_set_max_header_size(uint32_t size);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,82 +0,0 @@
#!/bin/bash
##===----------------------------------------------------------------------===##
##
## This source file is part of the SwiftNIO open source project
##
## Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
## Licensed under Apache License v2.0
##
## See LICENSE.txt for license information
## See CONTRIBUTORS.txt for the list of SwiftNIO project authors
##
## SPDX-License-Identifier: Apache-2.0
##
##===----------------------------------------------------------------------===##
set -eu
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
case "$(uname -s)" in
Darwin)
sed=gsed
;;
*)
sed=sed
;;
esac
if ! hash ${sed} 2>/dev/null; then
echo "You need sed \"${sed}\" to run this script ..."
echo
echo "On macOS: brew install gnu-sed"
exit 42
fi
for f in LICENSE-MIT AUTHORS; do
curl -o "${f}" \
-Ls "https://raw.githubusercontent.com/nodejs/http-parser/main/${f}"
done
for f in http_parser.c http_parser.h; do
( echo "/* Additional changes for SwiftNIO:"
echo " - prefixed all symbols by 'c_nio_'"
echo "*/"
curl -Ls "https://raw.githubusercontent.com/nodejs/http-parser/main/$f"
) > "$here/c_nio_$f"
"$sed" -i \
-e 's#"http_parser.h"#"include/c_nio_http_parser.h"#g' \
-e 's/\b\(http_body_is_final\)/c_nio_\1/g' \
-e 's/\b\(http_errno_description\)/c_nio_\1/g' \
-e 's/\b\(http_errno_name\)/c_nio_\1/g' \
-e 's/\b\(http_message_needs_eof\)/c_nio_\1/g' \
-e 's/\b\(http_method_str\)/c_nio_\1/g' \
-e 's/\b\(http_parser_execute\)/c_nio_\1/g' \
-e 's/\b\(http_parser_init\)/c_nio_\1/g' \
-e 's/\b\(http_parser_parse_url\)/c_nio_\1/g' \
-e 's/\b\(http_parser_pause\)/c_nio_\1/g' \
-e 's/\b\(http_parser_settings_init\)/c_nio_\1/g' \
-e 's/\b\(http_parser_url_init\)/c_nio_\1/g' \
-e 's/\b\(http_parser_version\)/c_nio_\1/g' \
-e 's/\b\(http_should_keep_alive\)/c_nio_\1/g' \
-e 's/\b\(http_status_str\)/c_nio_\1/g' \
-e 's/\b\(http_parser_set_max_header_size\)/c_nio_\1/g' \
"$here/c_nio_$f"
done
mv "$here/c_nio_http_parser.h" "$here/include/c_nio_http_parser.h"
tmp=$(mktemp -d /tmp/.test_compile_XXXXXX)
clang -o "$tmp/test.o" -c "$here/c_nio_http_parser.c"
num_non_nio=$(nm "$tmp/test.o" | grep ' T ' | grep -v c_nio | wc -l)
test 0 -eq $num_non_nio || {
echo "ERROR: $num_non_nio exported non-prefixed symbols found"
nm "$tmp/test.o" | grep ' T ' | grep -v c_nio
exit 1
}
rm -rf "$tmp"

View File

@ -0,0 +1,22 @@
This software is licensed under the MIT License.
Copyright Fedor Indutny, 2018.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,389 @@
/* Additional changes for SwiftNIO:
- prefixed all symbols by 'c_nio_'
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "include/c_nio_llhttp.h"
#define CALLBACK_MAYBE(PARSER, NAME) \
do { \
const llhttp_settings_t* settings; \
settings = (const llhttp_settings_t*) (PARSER)->settings; \
if (settings == NULL || settings->NAME == NULL) { \
err = 0; \
break; \
} \
err = settings->NAME((PARSER)); \
} while (0)
#define SPAN_CALLBACK_MAYBE(PARSER, NAME, START, LEN) \
do { \
const llhttp_settings_t* settings; \
settings = (const llhttp_settings_t*) (PARSER)->settings; \
if (settings == NULL || settings->NAME == NULL) { \
err = 0; \
break; \
} \
err = settings->NAME((PARSER), (START), (LEN)); \
if (err == -1) { \
err = HPE_USER; \
c_nio_llhttp_set_error_reason((PARSER), "Span callback error in " #NAME); \
} \
} while (0)
void c_nio_llhttp_init(llhttp_t* parser, llhttp_type_t type,
const llhttp_settings_t* settings) {
c_nio_llhttp__internal_init(parser);
parser->type = type;
parser->settings = (void*) settings;
}
#if defined(__wasm__)
extern int wasm_on_message_begin(llhttp_t * p);
extern int wasm_on_url(llhttp_t* p, const char* at, size_t length);
extern int wasm_on_status(llhttp_t* p, const char* at, size_t length);
extern int wasm_on_header_field(llhttp_t* p, const char* at, size_t length);
extern int wasm_on_header_value(llhttp_t* p, const char* at, size_t length);
extern int wasm_on_headers_complete(llhttp_t * p, int status_code,
uint8_t upgrade, int should_keep_alive);
extern int wasm_on_body(llhttp_t* p, const char* at, size_t length);
extern int wasm_on_message_complete(llhttp_t * p);
static int wasm_on_headers_complete_wrap(llhttp_t* p) {
return wasm_on_headers_complete(p, p->status_code, p->upgrade,
c_nio_llhttp_should_keep_alive(p));
}
const llhttp_settings_t wasm_settings = {
wasm_on_message_begin,
wasm_on_url,
wasm_on_status,
wasm_on_header_field,
wasm_on_header_value,
wasm_on_headers_complete_wrap,
wasm_on_body,
wasm_on_message_complete,
NULL,
NULL,
};
llhttp_t* llhttp_alloc(llhttp_type_t type) {
llhttp_t* parser = malloc(sizeof(llhttp_t));
c_nio_llhttp_init(parser, type, &wasm_settings);
return parser;
}
void llhttp_free(llhttp_t* parser) {
free(parser);
}
#endif // defined(__wasm__)
/* Some getters required to get stuff from the parser */
uint8_t c_nio_llhttp_get_type(llhttp_t* parser) {
return parser->type;
}
uint8_t c_nio_llhttp_get_http_major(llhttp_t* parser) {
return parser->http_major;
}
uint8_t c_nio_llhttp_get_http_minor(llhttp_t* parser) {
return parser->http_minor;
}
uint8_t c_nio_llhttp_get_method(llhttp_t* parser) {
return parser->method;
}
int c_nio_llhttp_get_status_code(llhttp_t* parser) {
return parser->status_code;
}
uint8_t c_nio_llhttp_get_upgrade(llhttp_t* parser) {
return parser->upgrade;
}
void c_nio_llhttp_reset(llhttp_t* parser) {
llhttp_type_t type = parser->type;
const llhttp_settings_t* settings = parser->settings;
void* data = parser->data;
uint8_t lenient_flags = parser->lenient_flags;
c_nio_llhttp__internal_init(parser);
parser->type = type;
parser->settings = (void*) settings;
parser->data = data;
parser->lenient_flags = lenient_flags;
}
llhttp_errno_t c_nio_llhttp_execute(llhttp_t* parser, const char* data, size_t len) {
return c_nio_llhttp__internal_execute(parser, data, data + len);
}
void c_nio_llhttp_settings_init(llhttp_settings_t* settings) {
memset(settings, 0, sizeof(*settings));
}
llhttp_errno_t c_nio_llhttp_finish(llhttp_t* parser) {
int err;
/* We're in an error state. Don't bother doing anything. */
if (parser->error != 0) {
return 0;
}
switch (parser->finish) {
case HTTP_FINISH_SAFE_WITH_CB:
CALLBACK_MAYBE(parser, on_message_complete);
if (err != HPE_OK) return err;
/* FALLTHROUGH */
case HTTP_FINISH_SAFE:
return HPE_OK;
case HTTP_FINISH_UNSAFE:
parser->reason = "Invalid EOF state";
return HPE_INVALID_EOF_STATE;
default:
abort();
}
}
void c_nio_llhttp_pause(llhttp_t* parser) {
if (parser->error != HPE_OK) {
return;
}
parser->error = HPE_PAUSED;
parser->reason = "Paused";
}
void c_nio_llhttp_resume(llhttp_t* parser) {
if (parser->error != HPE_PAUSED) {
return;
}
parser->error = 0;
}
void c_nio_llhttp_resume_after_upgrade(llhttp_t* parser) {
if (parser->error != HPE_PAUSED_UPGRADE) {
return;
}
parser->error = 0;
}
llhttp_errno_t c_nio_llhttp_get_errno(const llhttp_t* parser) {
return parser->error;
}
const char* c_nio_llhttp_get_error_reason(const llhttp_t* parser) {
return parser->reason;
}
void c_nio_llhttp_set_error_reason(llhttp_t* parser, const char* reason) {
parser->reason = reason;
}
const char* c_nio_llhttp_get_error_pos(const llhttp_t* parser) {
return parser->error_pos;
}
const char* c_nio_llhttp_errno_name(llhttp_errno_t err) {
#define HTTP_ERRNO_GEN(CODE, NAME, _) case HPE_##NAME: return "HPE_" #NAME;
switch (err) {
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
default: abort();
}
#undef HTTP_ERRNO_GEN
}
const char* c_nio_llhttp_method_name(llhttp_method_t method) {
#define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING;
switch (method) {
HTTP_ALL_METHOD_MAP(HTTP_METHOD_GEN)
default: abort();
}
#undef HTTP_METHOD_GEN
}
const char* c_nio_llhttp_status_name(llhttp_status_t status) {
#define HTTP_STATUS_GEN(NUM, NAME, STRING) case HTTP_STATUS_##NAME: return #STRING;
switch (status) {
HTTP_STATUS_MAP(HTTP_STATUS_GEN)
default: abort();
}
#undef HTTP_STATUS_GEN
}
void c_nio_llhttp_set_lenient_headers(llhttp_t* parser, int enabled) {
if (enabled) {
parser->lenient_flags |= LENIENT_HEADERS;
} else {
parser->lenient_flags &= ~LENIENT_HEADERS;
}
}
void c_nio_llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled) {
if (enabled) {
parser->lenient_flags |= LENIENT_CHUNKED_LENGTH;
} else {
parser->lenient_flags &= ~LENIENT_CHUNKED_LENGTH;
}
}
void c_nio_llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled) {
if (enabled) {
parser->lenient_flags |= LENIENT_KEEP_ALIVE;
} else {
parser->lenient_flags &= ~LENIENT_KEEP_ALIVE;
}
}
void c_nio_llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled) {
if (enabled) {
parser->lenient_flags |= LENIENT_TRANSFER_ENCODING;
} else {
parser->lenient_flags &= ~LENIENT_TRANSFER_ENCODING;
}
}
/* Callbacks */
int c_nio_llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_message_begin);
return err;
}
int c_nio_llhttp__on_url(llhttp_t* s, const char* p, const char* endp) {
int err;
SPAN_CALLBACK_MAYBE(s, on_url, p, endp - p);
return err;
}
int c_nio_llhttp__on_url_complete(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_url_complete);
return err;
}
int c_nio_llhttp__on_status(llhttp_t* s, const char* p, const char* endp) {
int err;
SPAN_CALLBACK_MAYBE(s, on_status, p, endp - p);
return err;
}
int c_nio_llhttp__on_status_complete(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_status_complete);
return err;
}
int c_nio_llhttp__on_header_field(llhttp_t* s, const char* p, const char* endp) {
int err;
SPAN_CALLBACK_MAYBE(s, on_header_field, p, endp - p);
return err;
}
int c_nio_llhttp__on_header_field_complete(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_header_field_complete);
return err;
}
int c_nio_llhttp__on_header_value(llhttp_t* s, const char* p, const char* endp) {
int err;
SPAN_CALLBACK_MAYBE(s, on_header_value, p, endp - p);
return err;
}
int c_nio_llhttp__on_header_value_complete(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_header_value_complete);
return err;
}
int c_nio_llhttp__on_headers_complete(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_headers_complete);
return err;
}
int c_nio_llhttp__on_message_complete(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_message_complete);
return err;
}
int c_nio_llhttp__on_body(llhttp_t* s, const char* p, const char* endp) {
int err;
SPAN_CALLBACK_MAYBE(s, on_body, p, endp - p);
return err;
}
int c_nio_llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_chunk_header);
return err;
}
int c_nio_llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_chunk_complete);
return err;
}
/* Private */
void c_nio_llhttp__debug(llhttp_t* s, const char* p, const char* endp,
const char* msg) {
if (p == endp) {
fprintf(stderr, "p=%p type=%d flags=%02x next=null debug=%s\n", s, s->type,
s->flags, msg);
} else {
fprintf(stderr, "p=%p type=%d flags=%02x next=%02x debug=%s\n", s,
s->type, s->flags, *p, msg);
}
}

View File

@ -0,0 +1,153 @@
/* Additional changes for SwiftNIO:
- prefixed all symbols by 'c_nio_'
*/
#include <stdio.h>
#ifndef LLHTTP__TEST
# include "include/c_nio_llhttp.h"
#else
# define llhttp_t llparse_t
#endif /* */
int c_nio_llhttp_message_needs_eof(const llhttp_t* parser);
int c_nio_llhttp_should_keep_alive(const llhttp_t* parser);
int c_nio_llhttp__before_headers_complete(llhttp_t* parser, const char* p,
const char* endp) {
/* Set this here so that on_headers_complete() callbacks can see it */
if ((parser->flags & F_UPGRADE) &&
(parser->flags & F_CONNECTION_UPGRADE)) {
/* For responses, "Upgrade: foo" and "Connection: upgrade" are
* mandatory only when it is a 101 Switching Protocols response,
* otherwise it is purely informational, to announce support.
*/
parser->upgrade =
(parser->type == HTTP_REQUEST || parser->status_code == 101);
} else {
parser->upgrade = (parser->method == HTTP_CONNECT);
}
return 0;
}
/* Return values:
* 0 - No body, `restart`, message_complete
* 1 - CONNECT request, `restart`, message_complete, and pause
* 2 - chunk_size_start
* 3 - body_identity
* 4 - body_identity_eof
* 5 - invalid transfer-encoding for request
*/
int c_nio_llhttp__after_headers_complete(llhttp_t* parser, const char* p,
const char* endp) {
int hasBody;
hasBody = parser->flags & F_CHUNKED || parser->content_length > 0;
if (parser->upgrade && (parser->method == HTTP_CONNECT ||
(parser->flags & F_SKIPBODY) || !hasBody)) {
/* Exit, the rest of the message is in a different protocol. */
return 1;
}
if (parser->flags & F_SKIPBODY) {
return 0;
} else if (parser->flags & F_CHUNKED) {
/* chunked encoding - ignore Content-Length header, prepare for a chunk */
return 2;
} else if (parser->flags & F_TRANSFER_ENCODING) {
if (parser->type == HTTP_REQUEST &&
(parser->lenient_flags & LENIENT_CHUNKED_LENGTH) == 0 &&
(parser->lenient_flags & LENIENT_TRANSFER_ENCODING) == 0) {
/* RFC 7230 3.3.3 */
/* If a Transfer-Encoding header field
* is present in a request and the chunked transfer coding is not
* the final encoding, the message body length cannot be determined
* reliably; the server MUST respond with the 400 (Bad Request)
* status code and then close the connection.
*/
return 5;
} else {
/* RFC 7230 3.3.3 */
/* If a Transfer-Encoding header field is present in a response and
* the chunked transfer coding is not the final encoding, the
* message body length is determined by reading the connection until
* it is closed by the server.
*/
return 4;
}
} else {
if (!(parser->flags & F_CONTENT_LENGTH)) {
if (!c_nio_llhttp_message_needs_eof(parser)) {
/* Assume content-length 0 - read the next */
return 0;
} else {
/* Read body until EOF */
return 4;
}
} else if (parser->content_length == 0) {
/* Content-Length header given but zero: Content-Length: 0\r\n */
return 0;
} else {
/* Content-Length header given and non-zero */
return 3;
}
}
}
int c_nio_llhttp__after_message_complete(llhttp_t* parser, const char* p,
const char* endp) {
int should_keep_alive;
should_keep_alive = c_nio_llhttp_should_keep_alive(parser);
parser->finish = HTTP_FINISH_SAFE;
parser->flags = 0;
/* NOTE: this is ignored in loose parsing mode */
return should_keep_alive;
}
int c_nio_llhttp_message_needs_eof(const llhttp_t* parser) {
if (parser->type == HTTP_REQUEST) {
return 0;
}
/* See RFC 2616 section 4.4 */
if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */
parser->status_code == 204 || /* No Content */
parser->status_code == 304 || /* Not Modified */
(parser->flags & F_SKIPBODY)) { /* response to a HEAD request */
return 0;
}
/* RFC 7230 3.3.3, see `c_nio_llhttp__after_headers_complete` */
if ((parser->flags & F_TRANSFER_ENCODING) &&
(parser->flags & F_CHUNKED) == 0) {
return 1;
}
if (parser->flags & (F_CHUNKED | F_CONTENT_LENGTH)) {
return 0;
}
return 1;
}
int c_nio_llhttp_should_keep_alive(const llhttp_t* parser) {
if (parser->http_major > 0 && parser->http_minor > 0) {
/* HTTP/1.1 */
if (parser->flags & F_CONNECTION_CLOSE) {
return 0;
}
} else {
/* HTTP/1.0 or earlier */
if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) {
return 0;
}
}
return !c_nio_llhttp_message_needs_eof(parser);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
// adaptions for llhttp to make it more straightforward to use from Swift
#ifndef C_NIO_LLHTTP_SWIFT
#define C_NIO_LLHTTP_SWIFT
#include "c_nio_llhttp.h"
static inline llhttp_errno_t c_nio_llhttp_execute_swift(llhttp_t *parser,
const void *data,
size_t len) {
return c_nio_llhttp_execute(parser, (const char *)data, len);
}
#endif

View File

@ -0,0 +1,724 @@
/* Additional changes for SwiftNIO:
- prefixed all symbols by 'c_nio_'
*/
#ifndef INCLUDE_LLHTTP_H_
#define INCLUDE_LLHTTP_H_
#define LLHTTP_VERSION_MAJOR 6
#define LLHTTP_VERSION_MINOR 0
#define LLHTTP_VERSION_PATCH 9
#ifndef LLHTTP_STRICT_MODE
# define LLHTTP_STRICT_MODE 0
#endif
#ifndef INCLUDE_LLHTTP_ITSELF_H_
#define INCLUDE_LLHTTP_ITSELF_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
typedef struct llhttp__internal_s llhttp__internal_t;
struct llhttp__internal_s {
int32_t _index;
void* _span_pos0;
void* _span_cb0;
int32_t error;
const char* reason;
const char* error_pos;
void* data;
void* _current;
uint64_t content_length;
uint8_t type;
uint8_t method;
uint8_t http_major;
uint8_t http_minor;
uint8_t header_state;
uint8_t lenient_flags;
uint8_t upgrade;
uint8_t finish;
uint16_t flags;
uint16_t status_code;
void* settings;
};
int c_nio_llhttp__internal_init(llhttp__internal_t* s);
int c_nio_llhttp__internal_execute(llhttp__internal_t* s, const char* p, const char* endp);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* INCLUDE_LLHTTP_ITSELF_H_ */
#ifndef LLLLHTTP_C_HEADERS_
#define LLLLHTTP_C_HEADERS_
#ifdef __cplusplus
extern "C" {
#endif
enum llhttp_errno {
HPE_OK = 0,
HPE_INTERNAL = 1,
HPE_STRICT = 2,
HPE_CR_EXPECTED = 25,
HPE_LF_EXPECTED = 3,
HPE_UNEXPECTED_CONTENT_LENGTH = 4,
HPE_UNEXPECTED_SPACE = 30,
HPE_CLOSED_CONNECTION = 5,
HPE_INVALID_METHOD = 6,
HPE_INVALID_URL = 7,
HPE_INVALID_CONSTANT = 8,
HPE_INVALID_VERSION = 9,
HPE_INVALID_HEADER_TOKEN = 10,
HPE_INVALID_CONTENT_LENGTH = 11,
HPE_INVALID_CHUNK_SIZE = 12,
HPE_INVALID_STATUS = 13,
HPE_INVALID_EOF_STATE = 14,
HPE_INVALID_TRANSFER_ENCODING = 15,
HPE_CB_MESSAGE_BEGIN = 16,
HPE_CB_HEADERS_COMPLETE = 17,
HPE_CB_MESSAGE_COMPLETE = 18,
HPE_CB_CHUNK_HEADER = 19,
HPE_CB_CHUNK_COMPLETE = 20,
HPE_PAUSED = 21,
HPE_PAUSED_UPGRADE = 22,
HPE_PAUSED_H2_UPGRADE = 23,
HPE_USER = 24,
HPE_CB_URL_COMPLETE = 26,
HPE_CB_STATUS_COMPLETE = 27,
HPE_CB_HEADER_FIELD_COMPLETE = 28,
HPE_CB_HEADER_VALUE_COMPLETE = 29
};
typedef enum llhttp_errno llhttp_errno_t;
enum llhttp_flags {
F_CONNECTION_KEEP_ALIVE = 0x1,
F_CONNECTION_CLOSE = 0x2,
F_CONNECTION_UPGRADE = 0x4,
F_CHUNKED = 0x8,
F_UPGRADE = 0x10,
F_CONTENT_LENGTH = 0x20,
F_SKIPBODY = 0x40,
F_TRAILING = 0x80,
F_TRANSFER_ENCODING = 0x200
};
typedef enum llhttp_flags llhttp_flags_t;
enum llhttp_lenient_flags {
LENIENT_HEADERS = 0x1,
LENIENT_CHUNKED_LENGTH = 0x2,
LENIENT_KEEP_ALIVE = 0x4,
LENIENT_TRANSFER_ENCODING = 0x8,
LENIENT_VERSION = 0x10
};
typedef enum llhttp_lenient_flags llhttp_lenient_flags_t;
enum llhttp_type {
HTTP_BOTH = 0,
HTTP_REQUEST = 1,
HTTP_RESPONSE = 2
};
typedef enum llhttp_type llhttp_type_t;
enum c_nio_llhttp_finish {
HTTP_FINISH_SAFE = 0,
HTTP_FINISH_SAFE_WITH_CB = 1,
HTTP_FINISH_UNSAFE = 2
};
typedef enum c_nio_llhttp_finish c_nio_llhttp_finish_t;
enum llhttp_method {
HTTP_DELETE = 0,
HTTP_GET = 1,
HTTP_HEAD = 2,
HTTP_POST = 3,
HTTP_PUT = 4,
HTTP_CONNECT = 5,
HTTP_OPTIONS = 6,
HTTP_TRACE = 7,
HTTP_COPY = 8,
HTTP_LOCK = 9,
HTTP_MKCOL = 10,
HTTP_MOVE = 11,
HTTP_PROPFIND = 12,
HTTP_PROPPATCH = 13,
HTTP_SEARCH = 14,
HTTP_UNLOCK = 15,
HTTP_BIND = 16,
HTTP_REBIND = 17,
HTTP_UNBIND = 18,
HTTP_ACL = 19,
HTTP_REPORT = 20,
HTTP_MKACTIVITY = 21,
HTTP_CHECKOUT = 22,
HTTP_MERGE = 23,
HTTP_MSEARCH = 24,
HTTP_NOTIFY = 25,
HTTP_SUBSCRIBE = 26,
HTTP_UNSUBSCRIBE = 27,
HTTP_PATCH = 28,
HTTP_PURGE = 29,
HTTP_MKCALENDAR = 30,
HTTP_LINK = 31,
HTTP_UNLINK = 32,
HTTP_SOURCE = 33,
HTTP_PRI = 34,
HTTP_DESCRIBE = 35,
HTTP_ANNOUNCE = 36,
HTTP_SETUP = 37,
HTTP_PLAY = 38,
HTTP_PAUSE = 39,
HTTP_TEARDOWN = 40,
HTTP_GET_PARAMETER = 41,
HTTP_SET_PARAMETER = 42,
HTTP_REDIRECT = 43,
HTTP_RECORD = 44,
HTTP_FLUSH = 45
};
typedef enum llhttp_method llhttp_method_t;
enum llhttp_status {
HTTP_STATUS_CONTINUE = 100,
HTTP_STATUS_SWITCHING_PROTOCOLS = 101,
HTTP_STATUS_PROCESSING = 102,
HTTP_STATUS_EARLY_HINTS = 103,
HTTP_STATUS_OK = 200,
HTTP_STATUS_CREATED = 201,
HTTP_STATUS_ACCEPTED = 202,
HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203,
HTTP_STATUS_NO_CONTENT = 204,
HTTP_STATUS_RESET_CONTENT = 205,
HTTP_STATUS_PARTIAL_CONTENT = 206,
HTTP_STATUS_MULTI_STATUS = 207,
HTTP_STATUS_ALREADY_REPORTED = 208,
HTTP_STATUS_IM_USED = 226,
HTTP_STATUS_MULTIPLE_CHOICES = 300,
HTTP_STATUS_MOVED_PERMANENTLY = 301,
HTTP_STATUS_FOUND = 302,
HTTP_STATUS_SEE_OTHER = 303,
HTTP_STATUS_NOT_MODIFIED = 304,
HTTP_STATUS_USE_PROXY = 305,
HTTP_STATUS_TEMPORARY_REDIRECT = 307,
HTTP_STATUS_PERMANENT_REDIRECT = 308,
HTTP_STATUS_BAD_REQUEST = 400,
HTTP_STATUS_UNAUTHORIZED = 401,
HTTP_STATUS_PAYMENT_REQUIRED = 402,
HTTP_STATUS_FORBIDDEN = 403,
HTTP_STATUS_NOT_FOUND = 404,
HTTP_STATUS_METHOD_NOT_ALLOWED = 405,
HTTP_STATUS_NOT_ACCEPTABLE = 406,
HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407,
HTTP_STATUS_REQUEST_TIMEOUT = 408,
HTTP_STATUS_CONFLICT = 409,
HTTP_STATUS_GONE = 410,
HTTP_STATUS_LENGTH_REQUIRED = 411,
HTTP_STATUS_PRECONDITION_FAILED = 412,
HTTP_STATUS_PAYLOAD_TOO_LARGE = 413,
HTTP_STATUS_URI_TOO_LONG = 414,
HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415,
HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416,
HTTP_STATUS_EXPECTATION_FAILED = 417,
HTTP_STATUS_IM_A_TEAPOT = 418,
HTTP_STATUS_MISDIRECTED_REQUEST = 421,
HTTP_STATUS_UNPROCESSABLE_ENTITY = 422,
HTTP_STATUS_LOCKED = 423,
HTTP_STATUS_FAILED_DEPENDENCY = 424,
HTTP_STATUS_TOO_EARLY = 425,
HTTP_STATUS_UPGRADE_REQUIRED = 426,
HTTP_STATUS_PRECONDITION_REQUIRED = 428,
HTTP_STATUS_TOO_MANY_REQUESTS = 429,
HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451,
HTTP_STATUS_INTERNAL_SERVER_ERROR = 500,
HTTP_STATUS_NOT_IMPLEMENTED = 501,
HTTP_STATUS_BAD_GATEWAY = 502,
HTTP_STATUS_SERVICE_UNAVAILABLE = 503,
HTTP_STATUS_GATEWAY_TIMEOUT = 504,
HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505,
HTTP_STATUS_VARIANT_ALSO_NEGOTIATES = 506,
HTTP_STATUS_INSUFFICIENT_STORAGE = 507,
HTTP_STATUS_LOOP_DETECTED = 508,
HTTP_STATUS_BANDWITH_LIMIT_EXCEEDED = 509,
HTTP_STATUS_NOT_EXTENDED = 510,
HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511
};
typedef enum llhttp_status llhttp_status_t;
#define HTTP_ERRNO_MAP(XX) \
XX(0, OK, OK) \
XX(1, INTERNAL, INTERNAL) \
XX(2, STRICT, STRICT) \
XX(25, CR_EXPECTED, CR_EXPECTED) \
XX(3, LF_EXPECTED, LF_EXPECTED) \
XX(4, UNEXPECTED_CONTENT_LENGTH, UNEXPECTED_CONTENT_LENGTH) \
XX(30, UNEXPECTED_SPACE, UNEXPECTED_SPACE) \
XX(5, CLOSED_CONNECTION, CLOSED_CONNECTION) \
XX(6, INVALID_METHOD, INVALID_METHOD) \
XX(7, INVALID_URL, INVALID_URL) \
XX(8, INVALID_CONSTANT, INVALID_CONSTANT) \
XX(9, INVALID_VERSION, INVALID_VERSION) \
XX(10, INVALID_HEADER_TOKEN, INVALID_HEADER_TOKEN) \
XX(11, INVALID_CONTENT_LENGTH, INVALID_CONTENT_LENGTH) \
XX(12, INVALID_CHUNK_SIZE, INVALID_CHUNK_SIZE) \
XX(13, INVALID_STATUS, INVALID_STATUS) \
XX(14, INVALID_EOF_STATE, INVALID_EOF_STATE) \
XX(15, INVALID_TRANSFER_ENCODING, INVALID_TRANSFER_ENCODING) \
XX(16, CB_MESSAGE_BEGIN, CB_MESSAGE_BEGIN) \
XX(17, CB_HEADERS_COMPLETE, CB_HEADERS_COMPLETE) \
XX(18, CB_MESSAGE_COMPLETE, CB_MESSAGE_COMPLETE) \
XX(19, CB_CHUNK_HEADER, CB_CHUNK_HEADER) \
XX(20, CB_CHUNK_COMPLETE, CB_CHUNK_COMPLETE) \
XX(21, PAUSED, PAUSED) \
XX(22, PAUSED_UPGRADE, PAUSED_UPGRADE) \
XX(23, PAUSED_H2_UPGRADE, PAUSED_H2_UPGRADE) \
XX(24, USER, USER) \
XX(26, CB_URL_COMPLETE, CB_URL_COMPLETE) \
XX(27, CB_STATUS_COMPLETE, CB_STATUS_COMPLETE) \
XX(28, CB_HEADER_FIELD_COMPLETE, CB_HEADER_FIELD_COMPLETE) \
XX(29, CB_HEADER_VALUE_COMPLETE, CB_HEADER_VALUE_COMPLETE) \
#define HTTP_METHOD_MAP(XX) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
XX(2, HEAD, HEAD) \
XX(3, POST, POST) \
XX(4, PUT, PUT) \
XX(5, CONNECT, CONNECT) \
XX(6, OPTIONS, OPTIONS) \
XX(7, TRACE, TRACE) \
XX(8, COPY, COPY) \
XX(9, LOCK, LOCK) \
XX(10, MKCOL, MKCOL) \
XX(11, MOVE, MOVE) \
XX(12, PROPFIND, PROPFIND) \
XX(13, PROPPATCH, PROPPATCH) \
XX(14, SEARCH, SEARCH) \
XX(15, UNLOCK, UNLOCK) \
XX(16, BIND, BIND) \
XX(17, REBIND, REBIND) \
XX(18, UNBIND, UNBIND) \
XX(19, ACL, ACL) \
XX(20, REPORT, REPORT) \
XX(21, MKACTIVITY, MKACTIVITY) \
XX(22, CHECKOUT, CHECKOUT) \
XX(23, MERGE, MERGE) \
XX(24, MSEARCH, M-SEARCH) \
XX(25, NOTIFY, NOTIFY) \
XX(26, SUBSCRIBE, SUBSCRIBE) \
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
XX(28, PATCH, PATCH) \
XX(29, PURGE, PURGE) \
XX(30, MKCALENDAR, MKCALENDAR) \
XX(31, LINK, LINK) \
XX(32, UNLINK, UNLINK) \
XX(33, SOURCE, SOURCE) \
#define RTSP_METHOD_MAP(XX) \
XX(1, GET, GET) \
XX(3, POST, POST) \
XX(6, OPTIONS, OPTIONS) \
XX(35, DESCRIBE, DESCRIBE) \
XX(36, ANNOUNCE, ANNOUNCE) \
XX(37, SETUP, SETUP) \
XX(38, PLAY, PLAY) \
XX(39, PAUSE, PAUSE) \
XX(40, TEARDOWN, TEARDOWN) \
XX(41, GET_PARAMETER, GET_PARAMETER) \
XX(42, SET_PARAMETER, SET_PARAMETER) \
XX(43, REDIRECT, REDIRECT) \
XX(44, RECORD, RECORD) \
XX(45, FLUSH, FLUSH) \
#define HTTP_ALL_METHOD_MAP(XX) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
XX(2, HEAD, HEAD) \
XX(3, POST, POST) \
XX(4, PUT, PUT) \
XX(5, CONNECT, CONNECT) \
XX(6, OPTIONS, OPTIONS) \
XX(7, TRACE, TRACE) \
XX(8, COPY, COPY) \
XX(9, LOCK, LOCK) \
XX(10, MKCOL, MKCOL) \
XX(11, MOVE, MOVE) \
XX(12, PROPFIND, PROPFIND) \
XX(13, PROPPATCH, PROPPATCH) \
XX(14, SEARCH, SEARCH) \
XX(15, UNLOCK, UNLOCK) \
XX(16, BIND, BIND) \
XX(17, REBIND, REBIND) \
XX(18, UNBIND, UNBIND) \
XX(19, ACL, ACL) \
XX(20, REPORT, REPORT) \
XX(21, MKACTIVITY, MKACTIVITY) \
XX(22, CHECKOUT, CHECKOUT) \
XX(23, MERGE, MERGE) \
XX(24, MSEARCH, M-SEARCH) \
XX(25, NOTIFY, NOTIFY) \
XX(26, SUBSCRIBE, SUBSCRIBE) \
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
XX(28, PATCH, PATCH) \
XX(29, PURGE, PURGE) \
XX(30, MKCALENDAR, MKCALENDAR) \
XX(31, LINK, LINK) \
XX(32, UNLINK, UNLINK) \
XX(33, SOURCE, SOURCE) \
XX(34, PRI, PRI) \
XX(35, DESCRIBE, DESCRIBE) \
XX(36, ANNOUNCE, ANNOUNCE) \
XX(37, SETUP, SETUP) \
XX(38, PLAY, PLAY) \
XX(39, PAUSE, PAUSE) \
XX(40, TEARDOWN, TEARDOWN) \
XX(41, GET_PARAMETER, GET_PARAMETER) \
XX(42, SET_PARAMETER, SET_PARAMETER) \
XX(43, REDIRECT, REDIRECT) \
XX(44, RECORD, RECORD) \
XX(45, FLUSH, FLUSH) \
#define HTTP_STATUS_MAP(XX) \
XX(100, CONTINUE, CONTINUE) \
XX(101, SWITCHING_PROTOCOLS, SWITCHING_PROTOCOLS) \
XX(102, PROCESSING, PROCESSING) \
XX(103, EARLY_HINTS, EARLY_HINTS) \
XX(200, OK, OK) \
XX(201, CREATED, CREATED) \
XX(202, ACCEPTED, ACCEPTED) \
XX(203, NON_AUTHORITATIVE_INFORMATION, NON_AUTHORITATIVE_INFORMATION) \
XX(204, NO_CONTENT, NO_CONTENT) \
XX(205, RESET_CONTENT, RESET_CONTENT) \
XX(206, PARTIAL_CONTENT, PARTIAL_CONTENT) \
XX(207, MULTI_STATUS, MULTI_STATUS) \
XX(208, ALREADY_REPORTED, ALREADY_REPORTED) \
XX(226, IM_USED, IM_USED) \
XX(300, MULTIPLE_CHOICES, MULTIPLE_CHOICES) \
XX(301, MOVED_PERMANENTLY, MOVED_PERMANENTLY) \
XX(302, FOUND, FOUND) \
XX(303, SEE_OTHER, SEE_OTHER) \
XX(304, NOT_MODIFIED, NOT_MODIFIED) \
XX(305, USE_PROXY, USE_PROXY) \
XX(307, TEMPORARY_REDIRECT, TEMPORARY_REDIRECT) \
XX(308, PERMANENT_REDIRECT, PERMANENT_REDIRECT) \
XX(400, BAD_REQUEST, BAD_REQUEST) \
XX(401, UNAUTHORIZED, UNAUTHORIZED) \
XX(402, PAYMENT_REQUIRED, PAYMENT_REQUIRED) \
XX(403, FORBIDDEN, FORBIDDEN) \
XX(404, NOT_FOUND, NOT_FOUND) \
XX(405, METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED) \
XX(406, NOT_ACCEPTABLE, NOT_ACCEPTABLE) \
XX(407, PROXY_AUTHENTICATION_REQUIRED, PROXY_AUTHENTICATION_REQUIRED) \
XX(408, REQUEST_TIMEOUT, REQUEST_TIMEOUT) \
XX(409, CONFLICT, CONFLICT) \
XX(410, GONE, GONE) \
XX(411, LENGTH_REQUIRED, LENGTH_REQUIRED) \
XX(412, PRECONDITION_FAILED, PRECONDITION_FAILED) \
XX(413, PAYLOAD_TOO_LARGE, PAYLOAD_TOO_LARGE) \
XX(414, URI_TOO_LONG, URI_TOO_LONG) \
XX(415, UNSUPPORTED_MEDIA_TYPE, UNSUPPORTED_MEDIA_TYPE) \
XX(416, RANGE_NOT_SATISFIABLE, RANGE_NOT_SATISFIABLE) \
XX(417, EXPECTATION_FAILED, EXPECTATION_FAILED) \
XX(418, IM_A_TEAPOT, IM_A_TEAPOT) \
XX(421, MISDIRECTED_REQUEST, MISDIRECTED_REQUEST) \
XX(422, UNPROCESSABLE_ENTITY, UNPROCESSABLE_ENTITY) \
XX(423, LOCKED, LOCKED) \
XX(424, FAILED_DEPENDENCY, FAILED_DEPENDENCY) \
XX(425, TOO_EARLY, TOO_EARLY) \
XX(426, UPGRADE_REQUIRED, UPGRADE_REQUIRED) \
XX(428, PRECONDITION_REQUIRED, PRECONDITION_REQUIRED) \
XX(429, TOO_MANY_REQUESTS, TOO_MANY_REQUESTS) \
XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, REQUEST_HEADER_FIELDS_TOO_LARGE) \
XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, UNAVAILABLE_FOR_LEGAL_REASONS) \
XX(500, INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR) \
XX(501, NOT_IMPLEMENTED, NOT_IMPLEMENTED) \
XX(502, BAD_GATEWAY, BAD_GATEWAY) \
XX(503, SERVICE_UNAVAILABLE, SERVICE_UNAVAILABLE) \
XX(504, GATEWAY_TIMEOUT, GATEWAY_TIMEOUT) \
XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP_VERSION_NOT_SUPPORTED) \
XX(506, VARIANT_ALSO_NEGOTIATES, VARIANT_ALSO_NEGOTIATES) \
XX(507, INSUFFICIENT_STORAGE, INSUFFICIENT_STORAGE) \
XX(508, LOOP_DETECTED, LOOP_DETECTED) \
XX(509, BANDWITH_LIMIT_EXCEEDED, BANDWITH_LIMIT_EXCEEDED) \
XX(510, NOT_EXTENDED, NOT_EXTENDED) \
XX(511, NETWORK_AUTHENTICATION_REQUIRED, NETWORK_AUTHENTICATION_REQUIRED) \
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* LLLLHTTP_C_HEADERS_ */
#ifndef INCLUDE_LLHTTP_API_H_
#define INCLUDE_LLHTTP_API_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#if defined(__wasm__)
#define LLHTTP_EXPORT __attribute__((visibility("default")))
#else
#define LLHTTP_EXPORT
#endif
typedef llhttp__internal_t llhttp_t;
typedef struct llhttp_settings_s llhttp_settings_t;
typedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length);
typedef int (*llhttp_cb)(llhttp_t*);
struct llhttp_settings_s {
/* Possible return values 0, -1, `HPE_PAUSED` */
llhttp_cb on_message_begin;
/* Possible return values 0, -1, HPE_USER */
llhttp_data_cb on_url;
llhttp_data_cb on_status;
llhttp_data_cb on_header_field;
llhttp_data_cb on_header_value;
/* Possible return values:
* 0 - Proceed normally
* 1 - Assume that request/response has no body, and proceed to parsing the
* next message
* 2 - Assume absence of body (as above) and make `c_nio_llhttp_execute()` return
* `HPE_PAUSED_UPGRADE`
* -1 - Error
* `HPE_PAUSED`
*/
llhttp_cb on_headers_complete;
/* Possible return values 0, -1, HPE_USER */
llhttp_data_cb on_body;
/* Possible return values 0, -1, `HPE_PAUSED` */
llhttp_cb on_message_complete;
llhttp_cb on_url_complete;
llhttp_cb on_status_complete;
llhttp_cb on_header_field_complete;
llhttp_cb on_header_value_complete;
/* When on_chunk_header is called, the current chunk length is stored
* in parser->content_length.
* Possible return values 0, -1, `HPE_PAUSED`
*/
llhttp_cb on_chunk_header;
llhttp_cb on_chunk_complete;
};
/* Initialize the parser with specific type and user settings.
*
* NOTE: lifetime of `settings` has to be at least the same as the lifetime of
* the `parser` here. In practice, `settings` has to be either a static
* variable or be allocated with `malloc`, `new`, etc.
*/
LLHTTP_EXPORT
void c_nio_llhttp_init(llhttp_t* parser, llhttp_type_t type,
const llhttp_settings_t* settings);
LLHTTP_EXPORT
llhttp_t* llhttp_alloc(llhttp_type_t type);
LLHTTP_EXPORT
void llhttp_free(llhttp_t* parser);
LLHTTP_EXPORT
uint8_t c_nio_llhttp_get_type(llhttp_t* parser);
LLHTTP_EXPORT
uint8_t c_nio_llhttp_get_http_major(llhttp_t* parser);
LLHTTP_EXPORT
uint8_t c_nio_llhttp_get_http_minor(llhttp_t* parser);
LLHTTP_EXPORT
uint8_t c_nio_llhttp_get_method(llhttp_t* parser);
LLHTTP_EXPORT
int c_nio_llhttp_get_status_code(llhttp_t* parser);
LLHTTP_EXPORT
uint8_t c_nio_llhttp_get_upgrade(llhttp_t* parser);
/* Reset an already initialized parser back to the start state, preserving the
* existing parser type, callback settings, user data, and lenient flags.
*/
LLHTTP_EXPORT
void c_nio_llhttp_reset(llhttp_t* parser);
/* Initialize the settings object */
LLHTTP_EXPORT
void c_nio_llhttp_settings_init(llhttp_settings_t* settings);
/* Parse full or partial request/response, invoking user callbacks along the
* way.
*
* If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing
* interrupts, and such errno is returned from `c_nio_llhttp_execute()`. If
* `HPE_PAUSED` was used as a errno, the execution can be resumed with
* `c_nio_llhttp_resume()` call.
*
* In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE`
* is returned after fully parsing the request/response. If the user wishes to
* continue parsing, they need to invoke `c_nio_llhttp_resume_after_upgrade()`.
*
* NOTE: if this function ever returns a non-pause type error, it will continue
* to return the same error upon each successive call up until `c_nio_llhttp_init()`
* is called.
*/
LLHTTP_EXPORT
llhttp_errno_t c_nio_llhttp_execute(llhttp_t* parser, const char* data, size_t len);
/* This method should be called when the other side has no further bytes to
* send (e.g. shutdown of readable side of the TCP connection.)
*
* Requests without `Content-Length` and other messages might require treating
* all incoming bytes as the part of the body, up to the last byte of the
* connection. This method will invoke `on_message_complete()` callback if the
* request was terminated safely. Otherwise a error code would be returned.
*/
LLHTTP_EXPORT
llhttp_errno_t c_nio_llhttp_finish(llhttp_t* parser);
/* Returns `1` if the incoming message is parsed until the last byte, and has
* to be completed by calling `c_nio_llhttp_finish()` on EOF
*/
LLHTTP_EXPORT
int c_nio_llhttp_message_needs_eof(const llhttp_t* parser);
/* Returns `1` if there might be any other messages following the last that was
* successfully parsed.
*/
LLHTTP_EXPORT
int c_nio_llhttp_should_keep_alive(const llhttp_t* parser);
/* Make further calls of `c_nio_llhttp_execute()` return `HPE_PAUSED` and set
* appropriate error reason.
*
* Important: do not call this from user callbacks! User callbacks must return
* `HPE_PAUSED` if pausing is required.
*/
LLHTTP_EXPORT
void c_nio_llhttp_pause(llhttp_t* parser);
/* Might be called to resume the execution after the pause in user's callback.
* See `c_nio_llhttp_execute()` above for details.
*
* Call this only if `c_nio_llhttp_execute()` returns `HPE_PAUSED`.
*/
LLHTTP_EXPORT
void c_nio_llhttp_resume(llhttp_t* parser);
/* Might be called to resume the execution after the pause in user's callback.
* See `c_nio_llhttp_execute()` above for details.
*
* Call this only if `c_nio_llhttp_execute()` returns `HPE_PAUSED_UPGRADE`
*/
LLHTTP_EXPORT
void c_nio_llhttp_resume_after_upgrade(llhttp_t* parser);
/* Returns the latest return error */
LLHTTP_EXPORT
llhttp_errno_t c_nio_llhttp_get_errno(const llhttp_t* parser);
/* Returns the verbal explanation of the latest returned error.
*
* Note: User callback should set error reason when returning the error. See
* `c_nio_llhttp_set_error_reason()` for details.
*/
LLHTTP_EXPORT
const char* c_nio_llhttp_get_error_reason(const llhttp_t* parser);
/* Assign verbal description to the returned error. Must be called in user
* callbacks right before returning the errno.
*
* Note: `HPE_USER` error code might be useful in user callbacks.
*/
LLHTTP_EXPORT
void c_nio_llhttp_set_error_reason(llhttp_t* parser, const char* reason);
/* Returns the pointer to the last parsed byte before the returned error. The
* pointer is relative to the `data` argument of `c_nio_llhttp_execute()`.
*
* Note: this method might be useful for counting the number of parsed bytes.
*/
LLHTTP_EXPORT
const char* c_nio_llhttp_get_error_pos(const llhttp_t* parser);
/* Returns textual name of error code */
LLHTTP_EXPORT
const char* c_nio_llhttp_errno_name(llhttp_errno_t err);
/* Returns textual name of HTTP method */
LLHTTP_EXPORT
const char* c_nio_llhttp_method_name(llhttp_method_t method);
/* Returns textual name of HTTP status */
LLHTTP_EXPORT
const char* c_nio_llhttp_status_name(llhttp_status_t status);
/* Enables/disables lenient header value parsing (disabled by default).
*
* Lenient parsing disables header value token checks, extending llhttp's
* protocol support to highly non-compliant clients/server. No
* `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when
* lenient parsing is "on".
*
* **(USE AT YOUR OWN RISK)**
*/
LLHTTP_EXPORT
void c_nio_llhttp_set_lenient_headers(llhttp_t* parser, int enabled);
/* Enables/disables lenient handling of conflicting `Transfer-Encoding` and
* `Content-Length` headers (disabled by default).
*
* Normally `llhttp` would error when `Transfer-Encoding` is present in
* conjunction with `Content-Length`. This error is important to prevent HTTP
* request smuggling, but may be less desirable for small number of cases
* involving legacy servers.
*
* **(USE AT YOUR OWN RISK)**
*/
LLHTTP_EXPORT
void c_nio_llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled);
/* Enables/disables lenient handling of `Connection: close` and HTTP/1.0
* requests responses.
*
* Normally `llhttp` would error on (in strict mode) or discard (in loose mode)
* the HTTP request/response after the request/response with `Connection: close`
* and `Content-Length`. This is important to prevent cache poisoning attacks,
* but might interact badly with outdated and insecure clients. With this flag
* the extra request/response will be parsed normally.
*
* **(USE AT YOUR OWN RISK)**
*/
void c_nio_llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled);
/* Enables/disables lenient handling of `Transfer-Encoding` header.
*
* Normally `llhttp` would error when a `Transfer-Encoding` has `chunked` value
* and another value after it (either in a single header or in multiple
* headers whose value are internally joined using `, `).
* This is mandated by the spec to reliably determine request body size and thus
* avoid request smuggling.
* With this flag the extra value will be parsed normally.
*
* **(USE AT YOUR OWN RISK)**
*/
void c_nio_llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* INCLUDE_LLHTTP_API_H_ */
#endif /* INCLUDE_LLHTTP_H_ */

View File

@ -0,0 +1,178 @@
#!/bin/bash
##===----------------------------------------------------------------------===##
##
## This source file is part of the SwiftNIO open source project
##
## Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
## Licensed under Apache License v2.0
##
## See LICENSE.txt for license information
## See CONTRIBUTORS.txt for the list of SwiftNIO project authors
##
## SPDX-License-Identifier: Apache-2.0
##
##===----------------------------------------------------------------------===##
set -eu
here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
case "$(uname -s)" in
Darwin)
sed=gsed
;;
*)
sed=sed
;;
esac
if ! hash ${sed} 2>/dev/null; then
echo "You need sed \"${sed}\" to run this script ..."
echo
echo "On macOS: brew install gnu-sed"
exit 42
fi
tmpdir=$(mktemp -d /tmp/.llhttp_vendor_XXXXXX)
cd "$tmpdir"
git clone https://github.com/nodejs/llhttp.git
cd llhttp
npm install
make
cp "$tmpdir/llhttp/LICENSE-MIT" "$here"
cp "$tmpdir/llhttp/build/llhttp.h" "$here"
cp "$tmpdir/llhttp/build/c/llhttp.c" "$here"
cp "$tmpdir/llhttp/src/native/"*.c "$here"
cd "$here"
# The sed script in here has gotten a little unwieldy, we should consider doing
# something smarter. For now it's good enough.
for f in *.{c,h}; do
( echo "/* Additional changes for SwiftNIO:"
echo " - prefixed all symbols by 'c_nio_'"
echo "*/"
) > "c_nio_$f"
cat "$f" >> "$here/c_nio_$f"
rm "$f"
"$sed" -i \
-e 's#"llhttp.h"#"include/c_nio_llhttp.h"#g' \
-e 's/\b\(llhttp__debug\)/c_nio_\1/g' \
-e 's/\b\(llhttp__on_body\)/c_nio_\1/g' \
-e 's/\b\(llhttp__on_chunk_complete\)/c_nio_\1/g' \
-e 's/\b\(llhttp__on_chunk_header\)/c_nio_\1/g' \
-e 's/\b\(llhttp__on_header_field\)/c_nio_\1/g' \
-e 's/\b\(llhttp__on_header_field_complete\)/c_nio_\1/g' \
-e 's/\b\(llhttp__on_header_value\)/c_nio_\1/g' \
-e 's/\b\(llhttp__on_header_value_complete\)/c_nio_\1/g' \
-e 's/\b\(llhttp__on_headers_complete\)/c_nio_\1/g' \
-e 's/\b\(llhttp__on_message_begin\)/c_nio_\1/g' \
-e 's/\b\(llhttp__on_message_complete\)/c_nio_\1/g' \
-e 's/\b\(llhttp__on_status\)/c_nio_\1/g' \
-e 's/\b\(llhttp__on_status_complete\)/c_nio_\1/g' \
-e 's/\b\(llhttp__on_url\)/c_nio_\1/g' \
-e 's/\b\(llhttp__on_url_complete\)/c_nio_\1/g' \
-e 's/\b\(llhttp_errno_name\)/c_nio_\1/g' \
-e 's/\b\(llhttp_execute\)/c_nio_\1/g' \
-e 's/\b\(llhttp_finish\)/c_nio_\1/g' \
-e 's/\b\(llhttp_get_errno\)/c_nio_\1/g' \
-e 's/\b\(llhttp_get_error_pos\)/c_nio_\1/g' \
-e 's/\b\(llhttp_get_error_reason\)/c_nio_\1/g' \
-e 's/\b\(llhttp_get_http_major\)/c_nio_\1/g' \
-e 's/\b\(llhttp_get_http_minor\)/c_nio_\1/g' \
-e 's/\b\(llhttp_get_method\)/c_nio_\1/g' \
-e 's/\b\(llhttp_get_status_code\)/c_nio_\1/g' \
-e 's/\b\(llhttp_get_type\)/c_nio_\1/g' \
-e 's/\b\(llhttp_get_upgrade\)/c_nio_\1/g' \
-e 's/\b\(llhttp_init\)/c_nio_\1/g' \
-e 's/\b\(llhttp_method_name\)/c_nio_\1/g' \
-e 's/\b\(llhttp_pause\)/c_nio_\1/g' \
-e 's/\b\(llhttp_reset\)/c_nio_\1/g' \
-e 's/\b\(llhttp_resume\)/c_nio_\1/g' \
-e 's/\b\(llhttp_resume_after_upgrade\)/c_nio_\1/g' \
-e 's/\b\(llhttp_set_error_reason\)/c_nio_\1/g' \
-e 's/\b\(llhttp_set_lenient_chunked_length\)/c_nio_\1/g' \
-e 's/\b\(llhttp_set_lenient_headers\)/c_nio_\1/g' \
-e 's/\b\(llhttp_set_lenient_keep_alive\)/c_nio_\1/g' \
-e 's/\b\(llhttp_set_lenient_transfer_encoding\)/c_nio_\1/g' \
-e 's/\b\(llhttp_settings_init\)/c_nio_\1/g' \
-e 's/\b\(llhttp_status_name\)/c_nio_\1/g' \
-e 's/\b\(llhttp__after_headers_complete\)/c_nio_\1/g' \
-e 's/\b\(llhttp__after_message_complete\)/c_nio_\1/g' \
-e 's/\b\(llhttp__before_headers_complete\)/c_nio_\1/g' \
-e 's/\b\(llhttp_message_needs_eof\)/c_nio_\1/g' \
-e 's/\b\(llhttp_should_keep_alive\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_and_flags\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_is_equal_content_length\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_is_equal_method\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_is_equal_upgrade\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_load_header_state\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_load_http_major\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_load_http_minor\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_load_method\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_load_type\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_mul_add_content_length\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_mul_add_content_length_1\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_mul_add_status_code\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_or_flags\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_or_flags_1\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_or_flags_15\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_or_flags_16\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_or_flags_18\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_or_flags_3\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_or_flags_4\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_or_flags_5\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_or_flags_6\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_store_header_state\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_store_http_major\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_store_http_minor\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_store_method\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_test_flags\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_test_flags_1\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_test_flags_2\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_test_flags_3\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_test_lenient_flags\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_test_lenient_flags_1\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_test_lenient_flags_2\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_test_lenient_flags_5\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_test_lenient_flags_7\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_content_length\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_finish\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_finish_1\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_finish_3\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_header_state\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_header_state_2\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_header_state_4\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_header_state_5\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_header_state_6\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_header_state_7\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_http_major\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_http_minor\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_status_code\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_type\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_type_1\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal__c_update_upgrade\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal_execute\)/c_nio_\1/g' \
-e 's/\b\(llhttp__internal_init\)/c_nio_\1/g' \
"$here/c_nio_$f"
done
mv "$here/c_nio_llhttp.h" "$here/include"
compiletmp=$(mktemp -d /tmp/.test_compile_XXXXXX)
for f in *.c; do
clang -o "$compiletmp/$f.o" -c "$here/$f"
num_non_nio=$(nm "$compiletmp/$f.o" | grep ' T ' | grep -v c_nio | wc -l)
test 0 -eq $num_non_nio || {
echo "ERROR: $num_non_nio exported non-prefixed symbols found"
nm "$compiletmp/$f.o" | grep ' T ' | grep -v c_nio
exit 1
}
done
rm -rf "$compiletmp"
rm -rf "$tmpdir"

View File

@ -2,7 +2,7 @@
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
@ -13,16 +13,12 @@
//===----------------------------------------------------------------------===//
import NIOCore
#if compiler(>=5.1)
@_implementationOnly import CNIOHTTPParser
#else
import CNIOHTTPParser
#endif
@_implementationOnly import CNIOLLHTTP
private extension UnsafeMutablePointer where Pointee == http_parser {
private extension UnsafeMutablePointer where Pointee == llhttp_t {
/// Returns the `KeepAliveState` for the current message that is parsed.
var keepAliveState: KeepAliveState {
return c_nio_http_should_keep_alive(self) == 0 ? .close : .keepAlive
return c_nio_llhttp_should_keep_alive(self) == 0 ? .close : .keepAlive
}
}
@ -38,15 +34,19 @@ private enum HTTPDecodingState {
}
private class BetterHTTPParser {
/// Maximum size of a HTTP header field name or value.
/// This number is derived largely from the historical behaviour of NIO.
private static let maximumHeaderFieldSize = 80 * 1024
var delegate: HTTPDecoderDelegate! = nil
private var parser: http_parser? = http_parser() // nil if unaccessible because reference passed away exclusively
private var settings = http_parser_settings()
private var parser: llhttp_t? = llhttp_t() // nil if unaccessible because reference passed away exclusively
private var settings: UnsafeMutablePointer<llhttp_settings_t>
private var decodingState: HTTPDecodingState = .beforeMessageBegin
private var firstNonDiscardableOffset: Int? = nil
private var currentFieldByteLength = 0
private var httpParserOffset = 0
private var rawBytesView: UnsafeRawBufferPointer = .init(start: UnsafeRawPointer(bitPattern: 0xcafbabe), count: 0)
private var httpErrno: http_errno? = nil
private var httpErrno: llhttp_errno_t? = nil
private var richerError: Error? = nil
private let kind: HTTPDecoderKind
var requestHeads = CircularBuffer<HTTPRequestHead>(initialCapacity: 1)
@ -54,63 +54,53 @@ private class BetterHTTPParser {
enum MessageContinuation {
case normal
case skipBody
case error(http_errno)
case error(llhttp_errno_t)
}
private static func fromOpaque(_ opaque: UnsafePointer<http_parser>?) -> BetterHTTPParser {
private static func fromOpaque(_ opaque: UnsafePointer<llhttp_t>?) -> BetterHTTPParser {
return Unmanaged<BetterHTTPParser>.fromOpaque(UnsafeRawPointer(opaque!.pointee.data)).takeUnretainedValue()
}
init(kind: HTTPDecoderKind) {
self.kind = kind
c_nio_http_parser_settings_init(&self.settings)
self.withExclusiveHTTPParser { parserPtr in
switch kind {
case .request:
c_nio_http_parser_init(parserPtr, HTTP_REQUEST)
case .response:
c_nio_http_parser_init(parserPtr, HTTP_RESPONSE)
}
}
self.settings.on_body = { opaque, bytes, len in
self.settings = UnsafeMutablePointer.allocate(capacity: 1)
c_nio_llhttp_settings_init(self.settings)
self.settings.pointee.on_body = { opaque, bytes, len in
BetterHTTPParser.fromOpaque(opaque).didReceiveBodyData(UnsafeRawBufferPointer(start: bytes, count: len))
return 0
}
self.settings.on_header_field = { opaque, bytes, len in
BetterHTTPParser.fromOpaque(opaque).didReceiveHeaderFieldData(UnsafeRawBufferPointer(start: bytes, count: len))
return 0
self.settings.pointee.on_header_field = { opaque, bytes, len in
return BetterHTTPParser.fromOpaque(opaque).didReceiveHeaderFieldData(UnsafeRawBufferPointer(start: bytes, count: len))
}
self.settings.on_header_value = { opaque, bytes, len in
BetterHTTPParser.fromOpaque(opaque).didReceiveHeaderValueData(UnsafeRawBufferPointer(start: bytes, count: len))
return 0
self.settings.pointee.on_header_value = { opaque, bytes, len in
return BetterHTTPParser.fromOpaque(opaque).didReceiveHeaderValueData(UnsafeRawBufferPointer(start: bytes, count: len))
}
self.settings.on_status = { opaque, bytes, len in
self.settings.pointee.on_status = { opaque, bytes, len in
BetterHTTPParser.fromOpaque(opaque).didReceiveStatusData(UnsafeRawBufferPointer(start: bytes, count: len))
return 0
}
self.settings.on_url = { opaque, bytes, len in
BetterHTTPParser.fromOpaque(opaque).didReceiveURLData(UnsafeRawBufferPointer(start: bytes, count: len))
return 0
self.settings.pointee.on_url = { opaque, bytes, len in
return BetterHTTPParser.fromOpaque(opaque).didReceiveURLData(UnsafeRawBufferPointer(start: bytes, count: len))
}
self.settings.on_chunk_complete = { opaque in
self.settings.pointee.on_chunk_complete = { opaque in
BetterHTTPParser.fromOpaque(opaque).didReceiveChunkCompleteNotification()
return 0
}
self.settings.on_chunk_header = { opaque in
self.settings.pointee.on_chunk_header = { opaque in
BetterHTTPParser.fromOpaque(opaque).didReceiveChunkHeaderNotification()
return 0
}
self.settings.on_message_begin = { opaque in
self.settings.pointee.on_message_begin = { opaque in
BetterHTTPParser.fromOpaque(opaque).didReceiveMessageBeginNotification()
return 0
}
self.settings.on_headers_complete = { opaque in
self.settings.pointee.on_headers_complete = { opaque in
let parser = BetterHTTPParser.fromOpaque(opaque)
switch parser.didReceiveHeadersCompleteNotification(versionMajor: Int(opaque!.pointee.http_major),
versionMinor: Int(opaque!.pointee.http_minor),
statusCode: Int(opaque!.pointee.status_code),
isUpgrade: opaque!.pointee.upgrade != 0,
method: http_method(rawValue: opaque!.pointee.method),
method: llhttp_method(rawValue: CUnsignedInt(opaque!.pointee.method)),
keepAliveState: opaque!.keepAliveState) {
case .normal:
return 0
@ -121,10 +111,22 @@ private class BetterHTTPParser {
return -1 // error
}
}
self.settings.on_message_complete = { opaque in
self.settings.pointee.on_message_complete = { opaque in
BetterHTTPParser.fromOpaque(opaque).didReceiveMessageCompleteNotification()
return 0
}
self.withExclusiveHTTPParser { parserPtr in
switch kind {
case .request:
c_nio_llhttp_init(parserPtr, HTTP_REQUEST, self.settings)
case .response:
c_nio_llhttp_init(parserPtr, HTTP_RESPONSE, self.settings)
}
}
}
deinit {
self.settings.deallocate()
}
private func start(bytes: UnsafeRawBufferPointer, newState: HTTPDecodingState) {
@ -133,21 +135,21 @@ private class BetterHTTPParser {
self.decodingState = newState
}
private func finish(_ callout: (inout HTTPDecoderDelegate, UnsafeRawBufferPointer) -> Void) {
private func finish(_ callout: (inout HTTPDecoderDelegate, UnsafeRawBufferPointer) throws -> Void) rethrows {
var currentFieldByteLength = 0
swap(&currentFieldByteLength, &self.currentFieldByteLength)
let start = self.rawBytesView.startIndex + self.firstNonDiscardableOffset!
let end = start + currentFieldByteLength
self.firstNonDiscardableOffset = nil
precondition(start >= self.rawBytesView.startIndex && end <= self.rawBytesView.endIndex)
callout(&self.delegate, .init(rebasing: self.rawBytesView[start ..< end]))
try callout(&self.delegate, .init(rebasing: self.rawBytesView[start ..< end]))
}
private func didReceiveBodyData(_ bytes: UnsafeRawBufferPointer) {
self.delegate.didReceiveBody(bytes)
}
private func didReceiveHeaderFieldData(_ bytes: UnsafeRawBufferPointer) {
private func didReceiveHeaderFieldData(_ bytes: UnsafeRawBufferPointer) -> CInt {
switch self.decodingState {
case .headerName, .trailerName:
()
@ -175,34 +177,39 @@ private class BetterHTTPParser {
case .beforeMessageBegin:
preconditionFailure()
}
self.currentFieldByteLength += bytes.count
return self.validateHeaderLength(bytes.count)
}
private func didReceiveHeaderValueData(_ bytes: UnsafeRawBufferPointer) {
switch self.decodingState {
case .headerValue, .trailerValue:
()
case .headerName:
self.finish { delegate, bytes in
delegate.didReceiveHeaderName(bytes)
private func didReceiveHeaderValueData(_ bytes: UnsafeRawBufferPointer) -> CInt {
do {
switch self.decodingState {
case .headerValue, .trailerValue:
()
case .headerName:
try self.finish { delegate, bytes in
try delegate.didReceiveHeaderName(bytes)
}
self.start(bytes: bytes, newState: .headerValue)
case .trailerName:
try self.finish { delegate, bytes in
try delegate.didReceiveTrailerName(bytes)
}
self.start(bytes: bytes, newState: .trailerValue)
case .beforeMessageBegin, .afterMessageBegin, .headersComplete, .url:
preconditionFailure()
}
self.start(bytes: bytes, newState: .headerValue)
case .trailerName:
self.finish { delegate, bytes in
delegate.didReceiveTrailerName(bytes)
}
self.start(bytes: bytes, newState: .trailerValue)
case .beforeMessageBegin, .afterMessageBegin, .headersComplete, .url:
preconditionFailure()
return self.validateHeaderLength(bytes.count)
} catch {
self.richerError = error
return -1
}
self.currentFieldByteLength += bytes.count
}
private func didReceiveStatusData(_ bytes: UnsafeRawBufferPointer) {
// we don't do anything special here because we'll need the whole 'head' anyway
}
private func didReceiveURLData(_ bytes: UnsafeRawBufferPointer) {
private func didReceiveURLData(_ bytes: UnsafeRawBufferPointer) -> CInt {
switch self.decodingState {
case .url:
()
@ -211,7 +218,7 @@ private class BetterHTTPParser {
case .beforeMessageBegin, .headersComplete, .headerName, .headerValue, .trailerName, .trailerValue:
preconditionFailure()
}
self.currentFieldByteLength += bytes.count
return self.validateHeaderLength(bytes.count)
}
private func didReceiveChunkCompleteNotification() {
@ -250,7 +257,7 @@ private class BetterHTTPParser {
versionMinor: Int,
statusCode: Int,
isUpgrade: Bool,
method: http_method,
method: llhttp_method,
keepAliveState: KeepAliveState) -> MessageContinuation {
switch self.decodingState {
case .headerValue:
@ -295,7 +302,7 @@ private class BetterHTTPParser {
// codes and override http_parser's handling as well.
guard !self.requestHeads.isEmpty else {
self.richerError = NIOHTTPDecoderError.unsolicitedResponse
return .error(HPE_UNKNOWN)
return .error(HPE_INTERNAL)
}
if 100 <= statusCode && statusCode < 200 && statusCode != 101 {
@ -341,9 +348,19 @@ private class BetterHTTPParser {
}
}
private func validateHeaderLength(_ newLength: Int) -> CInt {
self.currentFieldByteLength += newLength
if self.currentFieldByteLength > Self.maximumHeaderFieldSize {
self.richerError = HTTPParserError.headerOverflow
return -1
}
return 0
}
@inline(__always) // this need to be optimised away
func withExclusiveHTTPParser<T>(_ body: (UnsafeMutablePointer<http_parser>) -> T) -> T {
var parser: http_parser? = nil
func withExclusiveHTTPParser<T>(_ body: (UnsafeMutablePointer<llhttp_t>) -> T) -> T {
var parser: llhttp_t? = nil
assert(self.parser != nil, "parser must not be nil here, must be a re-entrancy issue")
swap(&parser, &self.parser)
defer {
@ -354,41 +371,58 @@ private class BetterHTTPParser {
}
func feedInput(_ bytes: UnsafeRawBufferPointer?) throws -> Int {
var parserErrno: UInt32 = 0
let parserConsumed = self.withExclusiveHTTPParser { parserPtr -> Int in
let parserResult: Int
var bytesRead = 0
let parserErrno: llhttp_errno_t = self.withExclusiveHTTPParser { parserPtr -> llhttp_errno_t in
var rc: llhttp_errno_t
if let bytes = bytes {
self.rawBytesView = bytes
defer {
self.rawBytesView = .init(start: UnsafeRawPointer(bitPattern: 0xdafbabe), count: 0)
}
parserResult = c_nio_http_parser_execute_swift(parserPtr,
&self.settings,
bytes.baseAddress! + self.httpParserOffset,
bytes.count - self.httpParserOffset)
let startPointer = bytes.baseAddress! + self.httpParserOffset
let bytesToRead = bytes.count - self.httpParserOffset
rc = c_nio_llhttp_execute_swift(parserPtr,
startPointer,
bytesToRead)
if rc == HPE_PAUSED_UPGRADE {
// This is a special pause. We don't need to stop here (our other code will prevent us
// parsing past this point, but we do need a special hook to work out how many bytes were read.
// The force-unwrap is safe: we know we hit an "error".
bytesRead = UnsafeRawPointer(c_nio_llhttp_get_error_pos(parserPtr)!) - startPointer
c_nio_llhttp_resume_after_upgrade(parserPtr)
rc = HPE_OK
} else {
bytesRead = bytesToRead
}
} else {
parserResult = c_nio_http_parser_execute(parserPtr, &self.settings, nil, 0)
rc = c_nio_llhttp_finish(parserPtr)
bytesRead = 0
}
parserErrno = parserPtr.pointee.http_errno
return parserResult
return rc
}
assert(parserConsumed >= 0)
// self.parser must be non-nil here because we can't be re-entered here (ByteToMessageDecoder guarantee)
guard parserErrno == 0 else {
guard parserErrno == HPE_OK else {
// if we chose to abort (eg. wrong HTTP version) the error will be in self.httpErrno, otherwise http_parser
// will tell us...
// self.parser must be non-nil here because we can't be re-entered here (ByteToMessageDecoder guarantee)
// If we have a richer error than the errno code, and the errno is unknown, we'll use it. Otherwise, we use the
// If we have a richer error than the errno code, and the errno is internal, we'll use it. Otherwise, we use the
// error from http_parser.
let err = self.httpErrno ?? http_errno(rawValue: parserErrno)
if err == HPE_UNKNOWN, let richerError = self.richerError {
let err = self.httpErrno ?? parserErrno
if (err == HPE_INTERNAL || err == HPE_USER), let richerError = self.richerError {
throw richerError
} else {
throw HTTPParserError.httpError(fromCHTTPParserErrno: err)!
}
}
if let firstNonDiscardableOffset = self.firstNonDiscardableOffset {
self.httpParserOffset += parserConsumed - firstNonDiscardableOffset
self.httpParserOffset += bytesRead - firstNonDiscardableOffset
self.firstNonDiscardableOffset = 0
return firstNonDiscardableOffset
} else {
@ -398,7 +432,7 @@ private class BetterHTTPParser {
//
// Set the HTTP parser offset back to zero, and tell the parent that we consumed
// the whole buffer.
let consumedBytes = self.httpParserOffset + parserConsumed
let consumedBytes = self.httpParserOffset + bytesRead
self.httpParserOffset = 0
return consumedBytes
}
@ -407,15 +441,15 @@ private class BetterHTTPParser {
private protocol HTTPDecoderDelegate {
mutating func didReceiveBody(_ bytes: UnsafeRawBufferPointer)
mutating func didReceiveHeaderName(_ bytes: UnsafeRawBufferPointer)
mutating func didReceiveHeaderName(_ bytes: UnsafeRawBufferPointer) throws
mutating func didReceiveHeaderValue(_ bytes: UnsafeRawBufferPointer)
mutating func didReceiveTrailerName(_ bytes: UnsafeRawBufferPointer)
mutating func didReceiveTrailerName(_ bytes: UnsafeRawBufferPointer) throws
mutating func didReceiveTrailerValue(_ bytes: UnsafeRawBufferPointer)
mutating func didReceiveURL(_ bytes: UnsafeRawBufferPointer)
mutating func didFinishHead(versionMajor: Int,
versionMinor: Int,
isUpgrade: Bool,
method: http_method,
method: llhttp_method,
statusCode: Int,
keepAliveState: KeepAliveState) -> Bool
mutating func didFinishMessage()
@ -533,8 +567,13 @@ public final class HTTPDecoder<In, Out>: ByteToMessageDecoder, HTTPDecoderDelega
}
func didReceiveHeaderName(_ bytes: UnsafeRawBufferPointer) {
func didReceiveHeaderName(_ bytes: UnsafeRawBufferPointer) throws {
assert(self.currentHeaderName == nil)
// Defensive check: llhttp tolerates a zero-length header field name, but we don't.
guard bytes.count > 0 else {
throw HTTPParserError.invalidHeaderToken
}
self.currentHeaderName = String(decoding: bytes, as: Unicode.UTF8.self)
}
@ -543,8 +582,13 @@ public final class HTTPDecoder<In, Out>: ByteToMessageDecoder, HTTPDecoderDelega
self.currentHeaderName = nil
}
func didReceiveTrailerName(_ bytes: UnsafeRawBufferPointer) {
func didReceiveTrailerName(_ bytes: UnsafeRawBufferPointer) throws {
assert(self.currentHeaderName == nil)
// Defensive check: llhttp tolerates a zero-length header field name, but we don't.
guard bytes.count > 0 else {
throw HTTPParserError.invalidHeaderToken
}
self.currentHeaderName = String(decoding: bytes, as: Unicode.UTF8.self)
}
@ -564,7 +608,7 @@ public final class HTTPDecoder<In, Out>: ByteToMessageDecoder, HTTPDecoderDelega
func didFinishHead(versionMajor: Int,
versionMinor: Int,
isUpgrade: Bool,
method: http_method,
method: llhttp_method,
statusCode: Int,
keepAliveState: KeepAliveState) -> Bool {
let message: NIOAny?
@ -737,52 +781,52 @@ extension HTTPParserError {
/// - Parameter fromCHTTPParserErrno: The error from the underlying library.
/// - Returns: The corresponding `HTTPParserError`, or `nil` if there is no
/// corresponding error.
static func httpError(fromCHTTPParserErrno: http_errno) -> HTTPParserError? {
static func httpError(fromCHTTPParserErrno: llhttp_errno_t) -> HTTPParserError? {
switch fromCHTTPParserErrno {
case HPE_INVALID_EOF_STATE:
return .invalidEOFState
case HPE_HEADER_OVERFLOW:
return .headerOverflow
case HPE_INTERNAL:
return .invalidInternalState
case HPE_STRICT:
return .strictModeAssertion
case HPE_LF_EXPECTED:
return .lfExpected
case HPE_UNEXPECTED_CONTENT_LENGTH:
return .unexpectedContentLength
case HPE_CLOSED_CONNECTION:
return .closedConnection
case HPE_INVALID_VERSION:
return .invalidVersion
case HPE_INVALID_STATUS:
return .invalidStatus
case HPE_INVALID_METHOD:
return .invalidMethod
case HPE_INVALID_URL:
return .invalidURL
case HPE_INVALID_HOST:
return .invalidHost
case HPE_INVALID_PORT:
return .invalidPort
case HPE_INVALID_PATH:
return .invalidPath
case HPE_INVALID_QUERY_STRING:
return .invalidQueryString
case HPE_INVALID_FRAGMENT:
return .invalidFragment
case HPE_LF_EXPECTED:
return .lfExpected
case HPE_INVALID_HEADER_TOKEN:
case HPE_INVALID_CONSTANT:
return .invalidConstant
case HPE_INVALID_VERSION:
return .invalidVersion
case HPE_INVALID_HEADER_TOKEN,
HPE_UNEXPECTED_SPACE:
return .invalidHeaderToken
case HPE_INVALID_CONTENT_LENGTH:
return .invalidContentLength
case HPE_UNEXPECTED_CONTENT_LENGTH:
return .unexpectedContentLength
case HPE_INVALID_CHUNK_SIZE:
return .invalidChunkSize
case HPE_INVALID_CONSTANT:
return .invalidConstant
case HPE_STRICT:
return .strictModeAssertion
case HPE_PAUSED:
case HPE_INVALID_STATUS:
return .invalidStatus
case HPE_INVALID_EOF_STATE:
return .invalidEOFState
case HPE_PAUSED, HPE_PAUSED_UPGRADE, HPE_PAUSED_H2_UPGRADE:
return .paused
case HPE_UNKNOWN:
return .unknown
case HPE_INVALID_TRANSFER_ENCODING:
// The downside of enums here, we don't have a case for this. Map it to .unknown for now.
case HPE_INVALID_TRANSFER_ENCODING,
HPE_CR_EXPECTED,
HPE_CB_MESSAGE_BEGIN,
HPE_CB_HEADERS_COMPLETE,
HPE_CB_MESSAGE_COMPLETE,
HPE_CB_CHUNK_HEADER,
HPE_CB_CHUNK_COMPLETE,
HPE_USER,
HPE_CB_URL_COMPLETE,
HPE_CB_STATUS_COMPLETE,
HPE_CB_HEADER_FIELD_COMPLETE,
HPE_CB_HEADER_VALUE_COMPLETE:
// The downside of enums here, we don't have a case for these. Map them to .unknown for now.
return .unknown
default:
return nil
@ -796,7 +840,7 @@ extension HTTPMethod {
///
/// - Parameter httpParserMethod: The method returned by `http_parser`.
/// - Returns: The corresponding `HTTPMethod`.
static func from(httpParserMethod: http_method) -> HTTPMethod {
static func from(httpParserMethod: llhttp_method) -> HTTPMethod {
switch httpParserMethod {
case HTTP_DELETE:
return .DELETE

View File

@ -796,10 +796,20 @@ public enum HTTPParserError: Error {
case invalidStatus
case invalidMethod
case invalidURL
@available(*, deprecated, message: "Cannot be thrown")
case invalidHost
@available(*, deprecated, message: "Cannot be thrown")
case invalidPort
@available(*, deprecated, message: "Cannot be thrown")
case invalidPath
@available(*, deprecated, message: "Cannot be thrown")
case invalidQueryString
@available(*, deprecated, message: "Cannot be thrown")
case invalidFragment
case lfExpected
case invalidHeaderToken

View File

@ -2,7 +2,7 @@
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2018-2021 Apple Inc. and the SwiftNIO project authors
// Copyright (c) 2018-2022 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
@ -30,11 +30,9 @@ extension HTTPDecoderTest {
("testDoesNotDecodeRealHTTP09Request", testDoesNotDecodeRealHTTP09Request),
("testDoesNotDecodeFakeHTTP09Request", testDoesNotDecodeFakeHTTP09Request),
("testDoesNotDecodeHTTP2XRequest", testDoesNotDecodeHTTP2XRequest),
("testToleratesHTTP13Request", testToleratesHTTP13Request),
("testDoesNotDecodeRealHTTP09Response", testDoesNotDecodeRealHTTP09Response),
("testDoesNotDecodeFakeHTTP09Response", testDoesNotDecodeFakeHTTP09Response),
("testDoesNotDecodeHTTP2XResponse", testDoesNotDecodeHTTP2XResponse),
("testToleratesHTTP13Response", testToleratesHTTP13Response),
("testCorrectlyMaintainIndicesWhenDiscardReadBytes", testCorrectlyMaintainIndicesWhenDiscardReadBytes),
("testDropExtraBytes", testDropExtraBytes),
("testDontDropExtraBytesRequest", testDontDropExtraBytesRequest),
@ -60,6 +58,9 @@ extension HTTPDecoderTest {
("testRefusesRequestSmugglingAttempt", testRefusesRequestSmugglingAttempt),
("testTrimsTrailingOWS", testTrimsTrailingOWS),
("testMassiveChunkDoesNotBufferAndGivesUsHoweverMuchIsAvailable", testMassiveChunkDoesNotBufferAndGivesUsHoweverMuchIsAvailable),
("testDecodingLongHeaderFieldNames", testDecodingLongHeaderFieldNames),
("testDecodingLongHeaderFieldValues", testDecodingLongHeaderFieldValues),
("testDecodingLongURLs", testDecodingLongURLs),
]
}
}

View File

@ -78,18 +78,6 @@ class HTTPDecoderTest: XCTestCase {
self.loop.run()
}
func testToleratesHTTP13Request() throws {
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder())).wait())
// We tolerate higher versions of HTTP/1 than we know about because RFC 7230
// says that these should be treated like HTTP/1.1 by our users.
var buffer = channel.allocator.buffer(capacity: 64)
buffer.writeStaticString("GET / HTTP/1.3\r\nHost: whatever\r\n\r\n")
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertNoThrow(try channel.finish())
}
func testDoesNotDecodeRealHTTP09Response() throws {
XCTAssertNoThrow(try channel.pipeline.addHandler(HTTPRequestEncoder()).wait())
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(HTTPResponseDecoder())).wait())
@ -146,22 +134,6 @@ class HTTPDecoderTest: XCTestCase {
self.loop.run()
}
func testToleratesHTTP13Response() throws {
XCTAssertNoThrow(try channel.pipeline.addHandler(HTTPRequestEncoder()).wait())
XCTAssertNoThrow(try channel.pipeline.addHandler(ByteToMessageHandler(HTTPResponseDecoder())).wait())
// We need to prime the decoder by seeing a GET request.
try channel.writeOutbound(HTTPClientRequestPart.head(HTTPRequestHead(version: .http2, method: .GET, uri: "/")))
// We tolerate higher versions of HTTP/1 than we know about because RFC 7230
// says that these should be treated like HTTP/1.1 by our users.
var buffer = channel.allocator.buffer(capacity: 64)
buffer.writeStaticString("HTTP/1.3 200 OK\r\nServer: whatever\r\n\r\n")
XCTAssertNoThrow(try channel.writeInbound(buffer))
XCTAssertNoThrow(try channel.finish())
}
func testCorrectlyMaintainIndicesWhenDiscardReadBytes() throws {
class Receiver: ChannelInboundHandler {
typealias InboundIn = HTTPServerRequestPart
@ -945,4 +917,82 @@ class HTTPDecoderTest: XCTestCase {
XCTAssertEqual(.invalidEOFState, error as? HTTPParserError)
}
}
func testDecodingLongHeaderFieldNames() {
// Our maximum field size is 80kB, so we're going to write an 80kB + 1 byte field name to confirm it fails.
XCTAssertNoThrow(try self.channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder())).wait())
var buffer = ByteBuffer(string: "GET / HTTP/1.1\r\nHost: example.com\r\n")
XCTAssertNoThrow(try self.channel.writeInbound(buffer))
XCTAssertNoThrow(XCTAssertNil(try self.channel.readInbound(as: HTTPServerRequestPart.self)))
buffer.clear()
buffer.writeRepeatingByte(UInt8(ascii: "x"), count: 1024)
for _ in 0..<80 {
XCTAssertNoThrow(try self.channel.writeInbound(buffer))
XCTAssertNoThrow(XCTAssertNil(try self.channel.readInbound(as: HTTPServerRequestPart.self)))
}
let lastByte = buffer.readSlice(length: 1)!
XCTAssertThrowsError(try self.channel.writeInbound(lastByte)) { error in
XCTAssertEqual(error as? HTTPParserError, .headerOverflow)
}
// We know we'll see an error, we can safely drop it.
_ = try? self.channel.finish()
}
func testDecodingLongHeaderFieldValues() {
// Our maximum field size is 80kB, so we're going to write an 80kB + 1 byte field value to confirm it fails.
XCTAssertNoThrow(try self.channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder())).wait())
var buffer = ByteBuffer(string: "GET / HTTP/1.1\r\nHost: example.com\r\nx: ")
XCTAssertNoThrow(try self.channel.writeInbound(buffer))
XCTAssertNoThrow(XCTAssertNil(try self.channel.readInbound(as: HTTPServerRequestPart.self)))
buffer.clear()
buffer.writeRepeatingByte(UInt8(ascii: "x"), count: 1024)
for _ in 0..<80 {
XCTAssertNoThrow(try self.channel.writeInbound(buffer))
XCTAssertNoThrow(XCTAssertNil(try self.channel.readInbound(as: HTTPServerRequestPart.self)))
}
let lastByte = buffer.readSlice(length: 1)!
XCTAssertThrowsError(try self.channel.writeInbound(lastByte)) { error in
XCTAssertEqual(error as? HTTPParserError, .headerOverflow)
}
// We know we'll see an error, we can safely drop it.
_ = try? self.channel.finish()
}
func testDecodingLongURLs() {
// Our maximum field size is 80kB, so we're going to write an 80kB + 1 byte URL to confirm it fails.
XCTAssertNoThrow(try self.channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder())).wait())
var buffer = ByteBuffer(string: "GET ")
XCTAssertNoThrow(try self.channel.writeInbound(buffer))
XCTAssertNoThrow(XCTAssertNil(try self.channel.readInbound(as: HTTPServerRequestPart.self)))
buffer.clear()
buffer.writeRepeatingByte(UInt8(ascii: "x"), count: 1024)
for _ in 0..<80 {
XCTAssertNoThrow(try self.channel.writeInbound(buffer))
XCTAssertNoThrow(XCTAssertNil(try self.channel.readInbound(as: HTTPServerRequestPart.self)))
}
let lastByte = buffer.readSlice(length: 1)!
XCTAssertThrowsError(try self.channel.writeInbound(lastByte)) { error in
XCTAssertEqual(error as? HTTPParserError, .headerOverflow)
}
// We know we'll see an error, we can safely drop it.
_ = try? self.channel.finish()
}
}

View File

@ -737,7 +737,7 @@ class HTTPServerPipelineHandlerTest: XCTestCase {
var state: State = .errorExpected
func errorCaught(context: ChannelHandlerContext, error: Error) {
XCTAssertEqual(HTTPParserError.headerOverflow, error as? HTTPParserError)
XCTAssertEqual(HTTPParserError.unknown, error as? HTTPParserError)
XCTAssertEqual(.errorExpected, self.state)
self.state = .done
}
@ -751,7 +751,7 @@ class HTTPServerPipelineHandlerTest: XCTestCase {
XCTAssertNoThrow(try self.channel.pipeline.addHandler(HTTPServerProtocolErrorHandler()).wait())
XCTAssertNoThrow(try self.channel.pipeline.addHandler(handler).wait())
self.channel.pipeline.fireErrorCaught(HTTPParserError.headerOverflow)
self.channel.pipeline.fireErrorCaught(HTTPParserError.unknown)
XCTAssertEqual(.done, handler.state)
}

View File

@ -35,7 +35,7 @@ services:
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=12050
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2050
- MAX_ALLOCS_ALLOWED_1000_udpconnections=84050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=409050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=411000
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2050
- MAX_ALLOCS_ALLOWED_creating_10000_headers=0
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2050

View File

@ -35,7 +35,7 @@ services:
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=12050
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2050
- MAX_ALLOCS_ALLOWED_1000_udpconnections=84050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=409050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=411000
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2050
- MAX_ALLOCS_ALLOWED_creating_10000_headers=0
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2050

View File

@ -35,7 +35,7 @@ services:
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=12050
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2050
- MAX_ALLOCS_ALLOWED_1000_udpconnections=81050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=406050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=408000
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2050
- MAX_ALLOCS_ALLOWED_creating_10000_headers=0
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2050

View File

@ -34,7 +34,7 @@ services:
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=12050
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2050
- MAX_ALLOCS_ALLOWED_1000_udpconnections=81050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=404050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=406000
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2050
- MAX_ALLOCS_ALLOWED_creating_10000_headers=0
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2050

View File

@ -34,7 +34,7 @@ services:
- MAX_ALLOCS_ALLOWED_1000_udp_reqs=12050
- MAX_ALLOCS_ALLOWED_1000_udpbootstraps=2050
- MAX_ALLOCS_ALLOWED_1000_udpconnections=81050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=403050
- MAX_ALLOCS_ALLOWED_1_reqs_1000_conn=405000
- MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=2050
- MAX_ALLOCS_ALLOWED_creating_10000_headers=0
- MAX_ALLOCS_ALLOWED_decode_1000_ws_frames=2050

View File

@ -63,7 +63,7 @@ for language in swift-or-c bash dtrace python; do
matching_files=( -name '*' )
case "$language" in
swift-or-c)
exceptions=( -name c_nio_http_parser.c -o -name c_nio_http_parser.h -o -name cpp_magic.h -o -name Package.swift -o -name 'Package@*.swift' -o -name CNIOSHA1.h -o -name c_nio_sha1.c -o -name ifaddrs-android.c -o -name ifaddrs-android.h)
exceptions=( -name c_nio_llhttp.c -o -name c_nio_api.c -o -name c_nio_http.c -o -name c_nio_llhttp.h -o -name cpp_magic.h -o -name Package.swift -o -name 'Package@*.swift' -o -name CNIOSHA1.h -o -name c_nio_sha1.c -o -name ifaddrs-android.c -o -name ifaddrs-android.h)
matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' )
cat > "$tmp" <<"EOF"
//===----------------------------------------------------------------------===//