diff --git a/cmake/FindLLHTTP.cmake b/cmake/FindLLHTTP.cmake new file mode 100644 index 000000000..a87d335d0 --- /dev/null +++ b/cmake/FindLLHTTP.cmake @@ -0,0 +1,39 @@ +# - Try to find llhttp +# +# Defines the following variables: +# +# LLHTTP_FOUND - system has llhttp +# LLHTTP_INCLUDE_DIR - the llhttp include directory +# LLHTTP_LIBRARIES - Link these to use llhttp +# LLHTTP_VERSION_MAJOR - major version +# LLHTTP_VERSION_MINOR - minor version +# LLHTTP_VERSION_STRING - the version of llhttp found + +# Find the header and library +find_path(LLHTTP_INCLUDE_DIR NAMES llhttp.h) +find_library(LLHTTP_LIBRARY NAMES llhttp libllhttp) + +# Found the header, read version +if(LLHTTP_INCLUDE_DIR AND EXISTS "${LLHTTP_INCLUDE_DIR}/llhttp.h") + file(READ "${LLHTTP_INCLUDE_DIR}/llhttp.h" LLHTTP_H) + if(LLHTTP_H) + string(REGEX REPLACE ".*#define[\t ]+LLHTTP_VERSION_MAJOR[\t ]+([0-9]+).*" "\\1" LLHTTP_VERSION_MAJOR "${LLHTTP_H}") + string(REGEX REPLACE ".*#define[\t ]+LLHTTP_VERSION_MINOR[\t ]+([0-9]+).*" "\\1" LLHTTP_VERSION_MINOR "${LLHTTP_H}") + set(LLHTTP_VERSION_STRING "${LLHTTP_VERSION_MAJOR}.${LLHTTP_VERSION_MINOR}") + endif() + unset(LLHTTP_H) +endif() + +# Handle the QUIETLY and REQUIRED arguments and set LLHTTP_FOUND +# to TRUE if all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LLHTTP REQUIRED_VARS LLHTTP_INCLUDE_DIR LLHTTP_LIBRARY) + +# Hide advanced variables +mark_as_advanced(LLHTTP_INCLUDE_DIR LLHTTP_LIBRARY) + +# Set standard variables +if(LLHTTP_FOUND) + set(LLHTTP_LIBRARIES ${LLHTTP_LIBRARY}) + set(LLHTTP_INCLUDE_DIRS ${LLHTTP_INCLUDE_DIR}) +endif() diff --git a/cmake/SelectHTTPParser.cmake b/cmake/SelectHTTPParser.cmake index 955aea330..aa6711d96 100644 --- a/cmake/SelectHTTPParser.cmake +++ b/cmake/SelectHTTPParser.cmake @@ -1,19 +1,39 @@ # Optional external dependency: http-parser -if(USE_HTTP_PARSER STREQUAL "system") - find_package(HTTPParser) +if(USE_HTTP_PARSER STREQUAL "builtin") + message(STATUS "support for bundled (legacy) http-parser explicitly requested") - if(HTTP_PARSER_FOUND AND HTTP_PARSER_VERSION_MAJOR EQUAL 2) - list(APPEND LIBGIT2_SYSTEM_INCLUDES ${HTTP_PARSER_INCLUDE_DIRS}) - list(APPEND LIBGIT2_SYSTEM_LIBS ${HTTP_PARSER_LIBRARIES}) - list(APPEND LIBGIT2_PC_LIBS "-lhttp_parser") - add_feature_info(http-parser ON "http-parser support (system)") - else() - message(FATAL_ERROR "http-parser support was requested but not found") - endif() -else() - message(STATUS "http-parser version 2 was not found or disabled; using bundled 3rd-party sources.") add_subdirectory("${PROJECT_SOURCE_DIR}/deps/http-parser" "${PROJECT_BINARY_DIR}/deps/http-parser") list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/http-parser") list(APPEND LIBGIT2_DEPENDENCY_OBJECTS "$") add_feature_info(http-parser ON "http-parser support (bundled)") +else() + # By default, try to use system LLHTTP. Fall back to + # system http-parser, and even to bundled http-parser + # as a last resort. + find_package(LLHTTP) + + if(LLHTTP_FOUND AND LLHTTP_VERSION_MAJOR EQUAL 9) + add_compile_definitions(USE_LLHTTP) + list(APPEND LIBGIT2_SYSTEM_INCLUDES ${LLHTTP_INCLUDE_DIRS}) + list(APPEND LIBGIT2_SYSTEM_LIBS ${LLHTTP_LIBRARIES}) + list(APPEND LIBGIT2_PC_LIBS "-lllhttp") + add_feature_info(llhttp ON "llhttp support (system)") + else() + message(STATUS "llhttp support was requested but not found; checking (legacy) http-parser support") + find_package(HTTPParser) + + if(HTTP_PARSER_FOUND AND HTTP_PARSER_VERSION_MAJOR EQUAL 2) + list(APPEND LIBGIT2_SYSTEM_INCLUDES ${HTTP_PARSER_INCLUDE_DIRS}) + list(APPEND LIBGIT2_SYSTEM_LIBS ${HTTP_PARSER_LIBRARIES}) + list(APPEND LIBGIT2_PC_LIBS "-lhttp_parser") + add_feature_info(http-parser ON "http-parser support (system)") + else() + message(STATUS "neither llhttp nor http-parser support was found; proceeding with bundled (legacy) http-parser") + + add_subdirectory("${PROJECT_SOURCE_DIR}/deps/http-parser" "${PROJECT_BINARY_DIR}/deps/http-parser") + list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/http-parser") + list(APPEND LIBGIT2_DEPENDENCY_OBJECTS "$") + add_feature_info(http-parser ON "http-parser support (bundled)") + endif() + endif() endif() diff --git a/src/libgit2/transports/http.c b/src/libgit2/transports/http.c index 8437674fc..fc2161b60 100644 --- a/src/libgit2/transports/http.c +++ b/src/libgit2/transports/http.c @@ -9,7 +9,6 @@ #ifndef GIT_WINHTTP -#include "http_parser.h" #include "net.h" #include "remote.h" #include "smart.h" diff --git a/src/libgit2/transports/httpclient.c b/src/libgit2/transports/httpclient.c index e22a07ba1..6e3491ed2 100644 --- a/src/libgit2/transports/httpclient.c +++ b/src/libgit2/transports/httpclient.c @@ -7,7 +7,32 @@ #include "common.h" #include "git2.h" -#include "http_parser.h" + +#ifdef USE_LLHTTP +#include +typedef llhttp_settings_t http_settings_t; +typedef llhttp_t http_parser_t; +GIT_INLINE(http_settings_t *) http_client_parser_settings(void); +#define git_http_parser_init(parser) llhttp_init(parser, HTTP_RESPONSE, http_client_parser_settings()) +#define git_http_parser_pause(parser) llhttp_pause(parser) +#define git_http_parser_resume(parser) llhttp_resume(parser) +#define git_http_parser_errno(parser) parser.error +#define git_http_should_keep_alive(parser) llhttp_should_keep_alive(parser) +#define git_http_errno_description(parser, errno) llhttp_get_error_reason(parser) +#else +#include +/* Legacy http-parser. */ +typedef http_parser_settings http_settings_t; +typedef struct http_parser http_parser_t; +GIT_INLINE(http_settings_t *) http_client_parser_settings(void); +#define git_http_parser_init(parser) http_parser_init(parser, HTTP_RESPONSE) +#define git_http_parser_pause(parser) http_parser_pause(parser, 1) +#define git_http_parser_resume(parser) http_parser_pause(parser, 0) +#define git_http_parser_errno(parser) parser.http_errno +#define git_http_should_keep_alive(parser) http_should_keep_alive(parser) +#define git_http_errno_description(parser, errno) http_errno_description(errno) +#endif /* USE_LLHTTP */ + #include "vector.h" #include "trace.h" #include "httpclient.h" @@ -108,7 +133,7 @@ struct git_http_client { git_http_server_t current_server; http_client_state state; - http_parser parser; + http_parser_t parser; git_http_server server; git_http_server proxy; @@ -154,7 +179,7 @@ void git_http_response_dispose(git_http_response *response) memset(response, 0, sizeof(git_http_response)); } -static int on_header_complete(http_parser *parser) +static int on_header_complete(http_parser_t *parser) { http_parser_context *ctx = (http_parser_context *) parser->data; git_http_client *client = ctx->client; @@ -219,7 +244,7 @@ static int on_header_complete(http_parser *parser) return 0; } -static int on_header_field(http_parser *parser, const char *str, size_t len) +static int on_header_field(http_parser_t *parser, const char *str, size_t len) { http_parser_context *ctx = (http_parser_context *) parser->data; @@ -254,7 +279,7 @@ static int on_header_field(http_parser *parser, const char *str, size_t len) return 0; } -static int on_header_value(http_parser *parser, const char *str, size_t len) +static int on_header_value(http_parser_t *parser, const char *str, size_t len) { http_parser_context *ctx = (http_parser_context *) parser->data; @@ -342,7 +367,7 @@ static int resend_needed(git_http_client *client, git_http_response *response) return 0; } -static int on_headers_complete(http_parser *parser) +static int on_headers_complete(http_parser_t *parser) { http_parser_context *ctx = (http_parser_context *) parser->data; @@ -365,7 +390,7 @@ static int on_headers_complete(http_parser *parser) } ctx->response->status = parser->status_code; - ctx->client->keepalive = http_should_keep_alive(parser); + ctx->client->keepalive = git_http_should_keep_alive(parser); /* Prepare for authentication */ collect_authinfo(&ctx->response->server_auth_schemetypes, @@ -378,18 +403,28 @@ static int on_headers_complete(http_parser *parser) ctx->response->resend_credentials = resend_needed(ctx->client, ctx->response); - /* Stop parsing. */ - http_parser_pause(parser, 1); +#ifndef USE_LLHTTP + /* Stop parsing. llhttp documentation says about llhttp_pause(): + * "Do not call this from user callbacks! User callbacks must + * return HPE_PAUSED if pausing is required", so that's what + * we will do, and call git_http_parser_pause() only for + * http-parser. */ + git_http_parser_pause(parser); +#endif if (ctx->response->content_type || ctx->response->chunked) ctx->client->state = READING_BODY; else ctx->client->state = DONE; +#ifdef USE_LLHTTP + return HPE_PAUSED; +#else return 0; +#endif } -static int on_body(http_parser *parser, const char *buf, size_t len) +static int on_body(http_parser_t *parser, const char *buf, size_t len) { http_parser_context *ctx = (http_parser_context *) parser->data; size_t max_len; @@ -411,7 +446,7 @@ static int on_body(http_parser *parser, const char *buf, size_t len) return 0; } -static int on_message_complete(http_parser *parser) +static int on_message_complete(http_parser_t *parser) { http_parser_context *ctx = (http_parser_context *) parser->data; @@ -878,7 +913,7 @@ GIT_INLINE(int) server_setup_from_url( static void reset_parser(git_http_client *client) { - http_parser_init(&client->parser, HTTP_RESPONSE); + git_http_parser_init(&client->parser); } static int setup_hosts( @@ -1122,9 +1157,46 @@ GIT_INLINE(int) client_read(git_http_client *client) } static bool parser_settings_initialized; -static http_parser_settings parser_settings; +static http_settings_t parser_settings; -GIT_INLINE(http_parser_settings *) http_client_parser_settings(void) +static size_t git_http_parser_execute(http_parser_t *parser, const char* data, size_t len) +{ +#ifdef USE_LLHTTP + llhttp_errno_t error; + size_t parsed_len; + + /* + * Unlike http_parser, which returns the number of parsed + * bytes in the _execute() call, llhttp returns an error + * code. + */ + + if (data == NULL || len == 0) { + error = llhttp_finish(parser); + } else { + error = llhttp_execute(parser, data, len); + } + + parsed_len = len; + /* + * Adjust number of parsed bytes in case of error. + */ + if (error != HPE_OK) { + parsed_len = llhttp_get_error_pos(parser) - data; + + /* This isn't a real pause, just a way to stop parsing early. */ + if (error == HPE_PAUSED_UPGRADE) { + llhttp_resume_after_upgrade(parser); + } + } + + return parsed_len; +#else + return http_parser_execute(parser, http_client_parser_settings(), data, len); +#endif +} + +GIT_INLINE(http_settings_t *) http_client_parser_settings(void) { if (!parser_settings_initialized) { parser_settings.on_header_field = on_header_field; @@ -1141,7 +1213,7 @@ GIT_INLINE(http_parser_settings *) http_client_parser_settings(void) GIT_INLINE(int) client_read_and_parse(git_http_client *client) { - http_parser *parser = &client->parser; + http_parser_t *parser = &client->parser; http_parser_context *ctx = (http_parser_context *) parser->data; unsigned char http_errno; int read_len; @@ -1155,11 +1227,10 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client) if (!client->read_buf.size && (read_len = client_read(client)) < 0) return read_len; - parsed_len = http_parser_execute(parser, - http_client_parser_settings(), + parsed_len = git_http_parser_execute(parser, client->read_buf.ptr, client->read_buf.size); - http_errno = client->parser.http_errno; + http_errno = git_http_parser_errno(client->parser); if (parsed_len > INT_MAX) { git_error_set(GIT_ERROR_HTTP, "unexpectedly large parse"); @@ -1179,6 +1250,7 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client) * where the server gives you a 100 and 200 simultaneously.) */ if (http_errno == HPE_PAUSED) { +#ifndef USE_LLHTTP /* * http-parser has a "feature" where it will not deliver the * final byte when paused in a callback. Consume that byte. @@ -1186,18 +1258,20 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client) */ GIT_ASSERT(client->read_buf.size > parsed_len); - http_parser_pause(parser, 0); +#endif + git_http_parser_resume(parser); - parsed_len += http_parser_execute(parser, - http_client_parser_settings(), +#ifndef USE_LLHTTP + parsed_len += git_http_parser_execute(parser, client->read_buf.ptr + parsed_len, 1); +#endif } /* Most failures will be reported in http_errno */ - else if (parser->http_errno != HPE_OK) { + else if (git_http_parser_errno(client->parser) != HPE_OK) { git_error_set(GIT_ERROR_HTTP, "http parser error: %s", - http_errno_description(http_errno)); + git_http_errno_description(parser, http_errno)); return -1; } @@ -1205,7 +1279,7 @@ GIT_INLINE(int) client_read_and_parse(git_http_client *client) else if (parsed_len != client->read_buf.size) { git_error_set(GIT_ERROR_HTTP, "http parser did not consume entire buffer: %s", - http_errno_description(http_errno)); + git_http_errno_description(parser, http_errno)); return -1; } diff --git a/src/util/net.c b/src/util/net.c index 447456451..dede784cc 100644 --- a/src/util/net.c +++ b/src/util/net.c @@ -11,7 +11,6 @@ #include "posix.h" #include "str.h" -#include "http_parser.h" #include "runtime.h" #define DEFAULT_PORT_HTTP "80"