From fc2378fadefe32755f11f33910b359d201864e45 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sat, 11 Apr 2015 15:46:36 -0400 Subject: [PATCH 01/67] lib,src,deps: switch from C++ to JS HTTP parser --- LICENSE | 27 - Makefile | 2 +- Makefile.build | 1 - configure | 21 - deps/http_parser/.gitignore | 28 - deps/http_parser/.mailmap | 8 - deps/http_parser/.travis.yml | 13 - deps/http_parser/AUTHORS | 67 - deps/http_parser/LICENSE-MIT | 23 - deps/http_parser/Makefile | 136 - deps/http_parser/README.md | 183 - deps/http_parser/contrib/parsertrace.c | 160 - deps/http_parser/contrib/url_parser.c | 46 - deps/http_parser/http_parser.c | 2429 ----------- deps/http_parser/http_parser.gyp | 111 - deps/http_parser/http_parser.h | 342 -- deps/http_parser/test.c | 3852 ----------------- doc/api/process.markdown | 4 +- lib/_http_client.js | 2 +- lib/_http_common.js | 86 +- lib/_http_parser.js | 703 +++ lib/_http_server.js | 2 +- lib/http.js | 40 +- node.gyp | 9 +- src/node.cc | 10 - src/node_http_parser.cc | 605 --- src/node_http_parser.h | 14 - test/parallel/test-http-blank-header.js | 2 +- test/parallel/test-http-client-parse-error.js | 4 +- test/parallel/test-http-methods.js | 12 - test/parallel/test-http-parser-bad-ref.js | 24 +- test/parallel/test-http-parser.js | 147 +- .../parallel/test-http-response-no-headers.js | 7 +- test/parallel/test-https-foafssl.js | 2 +- test/parallel/test-process-versions.js | 3 +- 35 files changed, 845 insertions(+), 8280 deletions(-) delete mode 100644 deps/http_parser/.gitignore delete mode 100644 deps/http_parser/.mailmap delete mode 100644 deps/http_parser/.travis.yml delete mode 100644 deps/http_parser/AUTHORS delete mode 100644 deps/http_parser/LICENSE-MIT delete mode 100644 deps/http_parser/Makefile delete mode 100644 deps/http_parser/README.md delete mode 100644 deps/http_parser/contrib/parsertrace.c delete mode 100644 deps/http_parser/contrib/url_parser.c delete mode 100644 deps/http_parser/http_parser.c delete mode 100644 deps/http_parser/http_parser.gyp delete mode 100644 deps/http_parser/http_parser.h delete mode 100644 deps/http_parser/test.c create mode 100644 lib/_http_parser.js delete mode 100644 src/node_http_parser.cc delete mode 100644 src/node_http_parser.h delete mode 100644 test/parallel/test-http-methods.js diff --git a/LICENSE b/LICENSE index f493fde3613eb5..8133c4c0bbb9df 100644 --- a/LICENSE +++ b/LICENSE @@ -186,33 +186,6 @@ The externally maintained libraries used by io.js are: */ """ -- HTTP Parser, located at deps/http_parser. HTTP Parser's license follows: - """ - http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright - Igor Sysoev. - - Additional changes are licensed under the same terms as NGINX and - 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. - """ - - Closure Linter is located at tools/closure_linter. Closure's license follows: """ diff --git a/Makefile b/Makefile index 51a950668d314e..e41f130788dbdb 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ $(NODE_G_EXE): config.gypi out/Makefile $(MAKE) -C out BUILDTYPE=Debug V=$(V) ln -fs out/Debug/$(NODE_EXE) $@ -out/Makefile: common.gypi deps/uv/uv.gyp deps/http_parser/http_parser.gyp deps/zlib/zlib.gyp deps/v8/build/toolchain.gypi deps/v8/build/features.gypi deps/v8/tools/gyp/v8.gyp node.gyp config.gypi +out/Makefile: common.gypi deps/uv/uv.gyp deps/zlib/zlib.gyp deps/v8/build/toolchain.gypi deps/v8/build/features.gypi deps/v8/tools/gyp/v8.gyp node.gyp config.gypi $(PYTHON) tools/gyp_node.py -f make config.gypi: configure diff --git a/Makefile.build b/Makefile.build index dad86cb517a9e6..8ede911ce16e03 100644 --- a/Makefile.build +++ b/Makefile.build @@ -233,7 +233,6 @@ NACL_ARCHES = nacl_ia32 nacl_x64 GYPFILES = \ common.gypi \ deps/cares/cares.gyp \ - deps/http_parser/http_parser.gyp \ deps/openssl/openssl.gyp \ deps/uv/uv.gyp \ deps/v8/tools/gyp/v8.gyp \ diff --git a/configure b/configure index 3f7992e0e4d482..8a50e2e59d3ca3 100755 --- a/configure +++ b/configure @@ -93,27 +93,7 @@ parser.add_option('--openssl-fips', dest='openssl_fips', help='Build OpenSSL using FIPS canister .o file in supplied folder') -shared_optgroup.add_option('--shared-http-parser', - action='store_true', - dest='shared_http_parser', - help='link to a shared http_parser DLL instead of static linking') - -shared_optgroup.add_option('--shared-http-parser-includes', - action='store', - dest='shared_http_parser_includes', - help='directory containing http_parser header files') - -shared_optgroup.add_option('--shared-http-parser-libname', - action='store', - dest='shared_http_parser_libname', - default='http_parser', help='alternative lib name to link to [default: %default]') - -shared_optgroup.add_option('--shared-http-parser-libpath', - action='store', - dest='shared_http_parser_libpath', - help='a directory to search for the shared http_parser DLL') - shared_optgroup.add_option('--shared-libuv', action='store_true', dest='shared_libuv', @@ -1017,7 +997,6 @@ flavor = GetFlavor(flavor_params) configure_node(output) configure_library('zlib', output) -configure_library('http_parser', output) configure_library('libuv', output) configure_v8(output) configure_openssl(output) diff --git a/deps/http_parser/.gitignore b/deps/http_parser/.gitignore deleted file mode 100644 index 32cb51b2d3f6f6..00000000000000 --- a/deps/http_parser/.gitignore +++ /dev/null @@ -1,28 +0,0 @@ -/out/ -core -tags -*.o -test -test_g -test_fast -bench -url_parser -parsertrace -parsertrace_g -*.mk -*.Makefile -*.so.* -*.a - - -# Visual Studio uglies -*.suo -*.sln -*.vcxproj -*.vcxproj.filters -*.vcxproj.user -*.opensdf -*.ncrunchsolution* -*.sdf -*.vsp -*.psess diff --git a/deps/http_parser/.mailmap b/deps/http_parser/.mailmap deleted file mode 100644 index 278d1412637240..00000000000000 --- a/deps/http_parser/.mailmap +++ /dev/null @@ -1,8 +0,0 @@ -# update AUTHORS with: -# git log --all --reverse --format='%aN <%aE>' | perl -ne 'BEGIN{print "# Authors ordered by first contribution.\n"} print unless $h{$_}; $h{$_} = 1' > AUTHORS -Ryan Dahl -Salman Haq -Simon Zimmermann -Thomas LE ROUX LE ROUX Thomas -Thomas LE ROUX Thomas LE ROUX -Fedor Indutny diff --git a/deps/http_parser/.travis.yml b/deps/http_parser/.travis.yml deleted file mode 100644 index 4b038e6e62d638..00000000000000 --- a/deps/http_parser/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: c - -compiler: - - clang - - gcc - -script: - - "make" - -notifications: - email: false - irc: - - "irc.freenode.net#node-ci" diff --git a/deps/http_parser/AUTHORS b/deps/http_parser/AUTHORS deleted file mode 100644 index 8e2df1d06e6f69..00000000000000 --- a/deps/http_parser/AUTHORS +++ /dev/null @@ -1,67 +0,0 @@ -# Authors ordered by first contribution. -Ryan Dahl -Jeremy Hinegardner -Sergey Shepelev -Joe Damato -tomika -Phoenix Sol -Cliff Frey -Ewen Cheslack-Postava -Santiago Gala -Tim Becker -Jeff Terrace -Ben Noordhuis -Nathan Rajlich -Mark Nottingham -Aman Gupta -Tim Becker -Sean Cunningham -Peter Griess -Salman Haq -Cliff Frey -Jon Kolb -Fouad Mardini -Paul Querna -Felix Geisendörfer -koichik -Andre Caron -Ivo Raisr -James McLaughlin -David Gwynne -Thomas LE ROUX -Randy Rizun -Andre Louis Caron -Simon Zimmermann -Erik Dubbelboer -Martell Malone -Bertrand Paquet -BogDan Vatra -Peter Faiman -Corey Richardson -Tóth Tamás -Cam Swords -Chris Dickinson -Uli Köhler -Charlie Somerville -Patrik Stutz -Fedor Indutny -runner -Alexis Campailla -David Wragg -Vinnie Falco -Alex Butum -Rex Feng -Alex Kocharin -Mark Koopman -Helge Heß -Alexis La Goutte -George Miroshnykov -Maciej Małecki -Marc O'Morain -Jeff Pinner -Timothy J Fontaine -Akagi201 -Romain Giraud -Jay Satiro -Arne Steen -Kjell Schubert diff --git a/deps/http_parser/LICENSE-MIT b/deps/http_parser/LICENSE-MIT deleted file mode 100644 index 58010b388945f9..00000000000000 --- a/deps/http_parser/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright -Igor Sysoev. - -Additional changes are licensed under the same terms as NGINX and -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. diff --git a/deps/http_parser/Makefile b/deps/http_parser/Makefile deleted file mode 100644 index 373709c6672e31..00000000000000 --- a/deps/http_parser/Makefile +++ /dev/null @@ -1,136 +0,0 @@ -# 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. - -PLATFORM ?= $(shell sh -c 'uname -s | tr "[A-Z]" "[a-z]"') -SONAME ?= libhttp_parser.so.2.5.0 - -CC?=gcc -AR?=ar - -CPPFLAGS ?= -LDFLAGS ?= - -CPPFLAGS += -I. -CPPFLAGS_DEBUG = $(CPPFLAGS) -DHTTP_PARSER_STRICT=1 -CPPFLAGS_DEBUG += $(CPPFLAGS_DEBUG_EXTRA) -CPPFLAGS_FAST = $(CPPFLAGS) -DHTTP_PARSER_STRICT=0 -CPPFLAGS_FAST += $(CPPFLAGS_FAST_EXTRA) -CPPFLAGS_BENCH = $(CPPFLAGS_FAST) - -CFLAGS += -Wall -Wextra -Werror -CFLAGS_DEBUG = $(CFLAGS) -O0 -g $(CFLAGS_DEBUG_EXTRA) -CFLAGS_FAST = $(CFLAGS) -O3 $(CFLAGS_FAST_EXTRA) -CFLAGS_BENCH = $(CFLAGS_FAST) -Wno-unused-parameter -CFLAGS_LIB = $(CFLAGS_FAST) -fPIC - -LDFLAGS_LIB = $(LDFLAGS) -shared - -INSTALL ?= install -PREFIX ?= $(DESTDIR)/usr/local -LIBDIR = $(PREFIX)/lib -INCLUDEDIR = $(PREFIX)/include - -ifneq (darwin,$(PLATFORM)) -# TODO(bnoordhuis) The native SunOS linker expects -h rather than -soname... -LDFLAGS_LIB += -Wl,-soname=$(SONAME) -endif - -test: test_g test_fast - ./test_g - ./test_fast - -test_g: http_parser_g.o test_g.o - $(CC) $(CFLAGS_DEBUG) $(LDFLAGS) http_parser_g.o test_g.o -o $@ - -test_g.o: test.c http_parser.h Makefile - $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) -c test.c -o $@ - -http_parser_g.o: http_parser.c http_parser.h Makefile - $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) -c http_parser.c -o $@ - -test_fast: http_parser.o test.o http_parser.h - $(CC) $(CFLAGS_FAST) $(LDFLAGS) http_parser.o test.o -o $@ - -test.o: test.c http_parser.h Makefile - $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) -c test.c -o $@ - -bench: http_parser.o bench.o - $(CC) $(CFLAGS_BENCH) $(LDFLAGS) http_parser.o bench.o -o $@ - -bench.o: bench.c http_parser.h Makefile - $(CC) $(CPPFLAGS_BENCH) $(CFLAGS_BENCH) -c bench.c -o $@ - -http_parser.o: http_parser.c http_parser.h Makefile - $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) -c http_parser.c - -test-run-timed: test_fast - while(true) do time ./test_fast > /dev/null; done - -test-valgrind: test_g - valgrind ./test_g - -libhttp_parser.o: http_parser.c http_parser.h Makefile - $(CC) $(CPPFLAGS_FAST) $(CFLAGS_LIB) -c http_parser.c -o libhttp_parser.o - -library: libhttp_parser.o - $(CC) $(LDFLAGS_LIB) -o $(SONAME) $< - -package: http_parser.o - $(AR) rcs libhttp_parser.a http_parser.o - -url_parser: http_parser.o contrib/url_parser.c - $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) $^ -o $@ - -url_parser_g: http_parser_g.o contrib/url_parser.c - $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) $^ -o $@ - -parsertrace: http_parser.o contrib/parsertrace.c - $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) $^ -o parsertrace - -parsertrace_g: http_parser_g.o contrib/parsertrace.c - $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) $^ -o parsertrace_g - -tags: http_parser.c http_parser.h test.c - ctags $^ - -install: library - $(INSTALL) -D http_parser.h $(INCLUDEDIR)/http_parser.h - $(INSTALL) -D $(SONAME) $(LIBDIR)/$(SONAME) - ln -s $(LIBDIR)/$(SONAME) $(LIBDIR)/libhttp_parser.so - -install-strip: library - $(INSTALL) -D http_parser.h $(INCLUDEDIR)/http_parser.h - $(INSTALL) -D -s $(SONAME) $(LIBDIR)/$(SONAME) - ln -s $(LIBDIR)/$(SONAME) $(LIBDIR)/libhttp_parser.so - -uninstall: - rm $(INCLUDEDIR)/http_parser.h - rm $(LIBDIR)/$(SONAME) - rm $(LIBDIR)/libhttp_parser.so - -clean: - rm -f *.o *.a tags test test_fast test_g \ - http_parser.tar libhttp_parser.so.* \ - url_parser url_parser_g parsertrace parsertrace_g - -contrib/url_parser.c: http_parser.h -contrib/parsertrace.c: http_parser.h - -.PHONY: clean package test-run test-run-timed test-valgrind install install-strip uninstall diff --git a/deps/http_parser/README.md b/deps/http_parser/README.md deleted file mode 100644 index 7c54dd42d087c3..00000000000000 --- a/deps/http_parser/README.md +++ /dev/null @@ -1,183 +0,0 @@ -HTTP Parser -=========== - -[![Build Status](https://travis-ci.org/joyent/http-parser.png?branch=master)](https://travis-ci.org/joyent/http-parser) - -This is a parser for HTTP messages written in C. It parses both requests and -responses. The parser is designed to be used in performance HTTP -applications. It does not make any syscalls nor allocations, it does not -buffer data, it can be interrupted at anytime. Depending on your -architecture, it only requires about 40 bytes of data per message -stream (in a web server that is per connection). - -Features: - - * No dependencies - * Handles persistent streams (keep-alive). - * Decodes chunked encoding. - * Upgrade support - * Defends against buffer overflow attacks. - -The parser extracts the following information from HTTP messages: - - * Header fields and values - * Content-Length - * Request method - * Response status code - * Transfer-Encoding - * HTTP version - * Request URL - * Message body - - -Usage ------ - -One `http_parser` object is used per TCP connection. Initialize the struct -using `http_parser_init()` and set the callbacks. That might look something -like this for a request parser: -```c -http_parser_settings settings; -settings.on_url = my_url_callback; -settings.on_header_field = my_header_field_callback; -/* ... */ - -http_parser *parser = malloc(sizeof(http_parser)); -http_parser_init(parser, HTTP_REQUEST); -parser->data = my_socket; -``` - -When data is received on the socket execute the parser and check for errors. - -```c -size_t len = 80*1024, nparsed; -char buf[len]; -ssize_t recved; - -recved = recv(fd, buf, len, 0); - -if (recved < 0) { - /* Handle error. */ -} - -/* Start up / continue the parser. - * Note we pass recved==0 to signal that EOF has been received. - */ -nparsed = http_parser_execute(parser, &settings, buf, recved); - -if (parser->upgrade) { - /* handle new protocol */ -} else if (nparsed != recved) { - /* Handle error. Usually just close the connection. */ -} -``` - -HTTP needs to know where the end of the stream is. For example, sometimes -servers send responses without Content-Length and expect the client to -consume input (for the body) until EOF. To tell http_parser about EOF, give -`0` as the fourth parameter to `http_parser_execute()`. Callbacks and errors -can still be encountered during an EOF, so one must still be prepared -to receive them. - -Scalar valued message information such as `status_code`, `method`, and the -HTTP version are stored in the parser structure. This data is only -temporally stored in `http_parser` and gets reset on each new message. If -this information is needed later, copy it out of the structure during the -`headers_complete` callback. - -The parser decodes the transfer-encoding for both requests and responses -transparently. That is, a chunked encoding is decoded before being sent to -the on_body callback. - - -The Special Problem of Upgrade ------------------------------- - -HTTP supports upgrading the connection to a different protocol. An -increasingly common example of this is the Web Socket protocol which sends -a request like - - GET /demo HTTP/1.1 - Upgrade: WebSocket - Connection: Upgrade - Host: example.com - Origin: http://example.com - WebSocket-Protocol: sample - -followed by non-HTTP data. - -(See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more -information the Web Socket protocol.) - -To support this, the parser will treat this as a normal HTTP message without a -body, issuing both on_headers_complete and on_message_complete callbacks. However -http_parser_execute() will stop parsing at the end of the headers and return. - -The user is expected to check if `parser->upgrade` has been set to 1 after -`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied -offset by the return value of `http_parser_execute()`. - - -Callbacks ---------- - -During the `http_parser_execute()` call, the callbacks set in -`http_parser_settings` will be executed. The parser maintains state and -never looks behind, so buffering the data is not necessary. If you need to -save certain data for later usage, you can do that from the callbacks. - -There are two types of callbacks: - -* notification `typedef int (*http_cb) (http_parser*);` - Callbacks: on_message_begin, on_headers_complete, on_message_complete. -* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);` - Callbacks: (requests only) on_url, - (common) on_header_field, on_header_value, on_body; - -Callbacks must return 0 on success. Returning a non-zero value indicates -error to the parser, making it exit immediately. - -In case you parse HTTP message in chunks (i.e. `read()` request line -from socket, parse, read half headers, parse, etc) your data callbacks -may be called more than once. Http-parser guarantees that data pointer is only -valid for the lifetime of callback. You can also `read()` into a heap allocated -buffer to avoid copying memory around if this fits your application. - -Reading headers may be a tricky task if you read/parse headers partially. -Basically, you need to remember whether last header callback was field or value -and apply the following logic: - - (on_header_field and on_header_value shortened to on_h_*) - ------------------------ ------------ -------------------------------------------- - | State (prev. callback) | Callback | Description/action | - ------------------------ ------------ -------------------------------------------- - | nothing (first call) | on_h_field | Allocate new buffer and copy callback data | - | | | into it | - ------------------------ ------------ -------------------------------------------- - | value | on_h_field | New header started. | - | | | Copy current name,value buffers to headers | - | | | list and allocate new buffer for new name | - ------------------------ ------------ -------------------------------------------- - | field | on_h_field | Previous name continues. Reallocate name | - | | | buffer and append callback data to it | - ------------------------ ------------ -------------------------------------------- - | field | on_h_value | Value for current header started. Allocate | - | | | new buffer and copy callback data to it | - ------------------------ ------------ -------------------------------------------- - | value | on_h_value | Value continues. Reallocate value buffer | - | | | and append callback data to it | - ------------------------ ------------ -------------------------------------------- - - -Parsing URLs ------------- - -A simplistic zero-copy URL parser is provided as `http_parser_parse_url()`. -Users of this library may wish to use it to parse URLs constructed from -consecutive `on_url` callbacks. - -See examples of reading in headers: - -* [partial example](http://gist.github.com/155877) in C -* [from http-parser tests](http://github.com/joyent/http-parser/blob/37a0ff8/test.c#L403) in C -* [from Node library](http://github.com/joyent/node/blob/842eaf4/src/http.js#L284) in Javascript diff --git a/deps/http_parser/contrib/parsertrace.c b/deps/http_parser/contrib/parsertrace.c deleted file mode 100644 index e7153680f467de..00000000000000 --- a/deps/http_parser/contrib/parsertrace.c +++ /dev/null @@ -1,160 +0,0 @@ -/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev - * - * Additional changes are licensed under the same terms as NGINX and - * 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. - */ - -/* Dump what the parser finds to stdout as it happen */ - -#include "http_parser.h" -#include -#include -#include - -int on_message_begin(http_parser* _) { - (void)_; - printf("\n***MESSAGE BEGIN***\n\n"); - return 0; -} - -int on_headers_complete(http_parser* _) { - (void)_; - printf("\n***HEADERS COMPLETE***\n\n"); - return 0; -} - -int on_message_complete(http_parser* _) { - (void)_; - printf("\n***MESSAGE COMPLETE***\n\n"); - return 0; -} - -int on_url(http_parser* _, const char* at, size_t length) { - (void)_; - printf("Url: %.*s\n", (int)length, at); - return 0; -} - -int on_header_field(http_parser* _, const char* at, size_t length) { - (void)_; - printf("Header field: %.*s\n", (int)length, at); - return 0; -} - -int on_header_value(http_parser* _, const char* at, size_t length) { - (void)_; - printf("Header value: %.*s\n", (int)length, at); - return 0; -} - -int on_body(http_parser* _, const char* at, size_t length) { - (void)_; - printf("Body: %.*s\n", (int)length, at); - return 0; -} - -void usage(const char* name) { - fprintf(stderr, - "Usage: %s $type $filename\n" - " type: -x, where x is one of {r,b,q}\n" - " parses file as a Response, reQuest, or Both\n", - name); - exit(EXIT_FAILURE); -} - -int main(int argc, char* argv[]) { - enum http_parser_type file_type; - - if (argc != 3) { - usage(argv[0]); - } - - char* type = argv[1]; - if (type[0] != '-') { - usage(argv[0]); - } - - switch (type[1]) { - /* in the case of "-", type[1] will be NUL */ - case 'r': - file_type = HTTP_RESPONSE; - break; - case 'q': - file_type = HTTP_REQUEST; - break; - case 'b': - file_type = HTTP_BOTH; - break; - default: - usage(argv[0]); - } - - char* filename = argv[2]; - FILE* file = fopen(filename, "r"); - if (file == NULL) { - perror("fopen"); - goto fail; - } - - fseek(file, 0, SEEK_END); - long file_length = ftell(file); - if (file_length == -1) { - perror("ftell"); - goto fail; - } - fseek(file, 0, SEEK_SET); - - char* data = malloc(file_length); - if (fread(data, 1, file_length, file) != (size_t)file_length) { - fprintf(stderr, "couldn't read entire file\n"); - free(data); - goto fail; - } - - http_parser_settings settings; - memset(&settings, 0, sizeof(settings)); - settings.on_message_begin = on_message_begin; - settings.on_url = on_url; - settings.on_header_field = on_header_field; - settings.on_header_value = on_header_value; - settings.on_headers_complete = on_headers_complete; - settings.on_body = on_body; - settings.on_message_complete = on_message_complete; - - http_parser parser; - http_parser_init(&parser, file_type); - size_t nparsed = http_parser_execute(&parser, &settings, data, file_length); - free(data); - - if (nparsed != (size_t)file_length) { - fprintf(stderr, - "Error: %s (%s)\n", - http_errno_description(HTTP_PARSER_ERRNO(&parser)), - http_errno_name(HTTP_PARSER_ERRNO(&parser))); - goto fail; - } - - return EXIT_SUCCESS; - -fail: - fclose(file); - return EXIT_FAILURE; -} diff --git a/deps/http_parser/contrib/url_parser.c b/deps/http_parser/contrib/url_parser.c deleted file mode 100644 index 6650b414af9065..00000000000000 --- a/deps/http_parser/contrib/url_parser.c +++ /dev/null @@ -1,46 +0,0 @@ -#include "http_parser.h" -#include -#include - -void -dump_url (const char *url, const struct http_parser_url *u) -{ - unsigned int i; - - printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port); - for (i = 0; i < UF_MAX; i++) { - if ((u->field_set & (1 << i)) == 0) { - printf("\tfield_data[%u]: unset\n", i); - continue; - } - - printf("\tfield_data[%u]: off: %u, len: %u, part: %.*s\n", - i, - u->field_data[i].off, - u->field_data[i].len, - u->field_data[i].len, - url + u->field_data[i].off); - } -} - -int main(int argc, char ** argv) { - struct http_parser_url u; - int len, connect, result; - - if (argc != 3) { - printf("Syntax : %s connect|get url\n", argv[0]); - return 1; - } - len = strlen(argv[2]); - connect = strcmp("connect", argv[1]) == 0 ? 1 : 0; - printf("Parsing %s, connect %d\n", argv[2], connect); - - result = http_parser_parse_url(argv[2], len, connect, &u); - if (result != 0) { - printf("Parse error : %d\n", result); - return result; - } - printf("Parse ok, result : \n"); - dump_url(argv[2], &u); - return 0; -} \ No newline at end of file diff --git a/deps/http_parser/http_parser.c b/deps/http_parser/http_parser.c deleted file mode 100644 index 0fa1c362729c4f..00000000000000 --- a/deps/http_parser/http_parser.c +++ /dev/null @@ -1,2429 +0,0 @@ -/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev - * - * Additional changes are licensed under the same terms as NGINX and - * 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. - */ -#include "http_parser.h" -#include -#include -#include -#include -#include -#include - -#ifndef ULLONG_MAX -# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ -#endif - -#ifndef MIN -# define MIN(a,b) ((a) < (b) ? (a) : (b)) -#endif - -#ifndef ARRAY_SIZE -# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) -#endif - -#ifndef BIT_AT -# define BIT_AT(a, i) \ - (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ - (1 << ((unsigned int) (i) & 7)))) -#endif - -#ifndef ELEM_AT -# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) -#endif - -#define SET_ERRNO(e) \ -do { \ - parser->http_errno = (e); \ -} while(0) - -#define CURRENT_STATE() p_state -#define UPDATE_STATE(V) p_state = (enum state) (V); -#define RETURN(V) \ -do { \ - parser->state = CURRENT_STATE(); \ - return (V); \ -} while (0); -#define REEXECUTE() \ - goto reexecute; \ - - -#ifdef __GNUC__ -# define LIKELY(X) __builtin_expect(!!(X), 1) -# define UNLIKELY(X) __builtin_expect(!!(X), 0) -#else -# define LIKELY(X) (X) -# define UNLIKELY(X) (X) -#endif - - -/* Run the notify callback FOR, returning ER if it fails */ -#define CALLBACK_NOTIFY_(FOR, ER) \ -do { \ - assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ - \ - if (LIKELY(settings->on_##FOR)) { \ - parser->state = CURRENT_STATE(); \ - if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ - SET_ERRNO(HPE_CB_##FOR); \ - } \ - UPDATE_STATE(parser->state); \ - \ - /* We either errored above or got paused; get out */ \ - if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ - return (ER); \ - } \ - } \ -} while (0) - -/* Run the notify callback FOR and consume the current byte */ -#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) - -/* Run the notify callback FOR and don't consume the current byte */ -#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) - -/* Run data callback FOR with LEN bytes, returning ER if it fails */ -#define CALLBACK_DATA_(FOR, LEN, ER) \ -do { \ - assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ - \ - if (FOR##_mark) { \ - if (LIKELY(settings->on_##FOR)) { \ - parser->state = CURRENT_STATE(); \ - if (UNLIKELY(0 != \ - settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ - SET_ERRNO(HPE_CB_##FOR); \ - } \ - UPDATE_STATE(parser->state); \ - \ - /* We either errored above or got paused; get out */ \ - if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ - return (ER); \ - } \ - } \ - FOR##_mark = NULL; \ - } \ -} while (0) - -/* Run the data callback FOR and consume the current byte */ -#define CALLBACK_DATA(FOR) \ - CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) - -/* Run the data callback FOR and don't consume the current byte */ -#define CALLBACK_DATA_NOADVANCE(FOR) \ - CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) - -/* Set the mark FOR; non-destructive if mark is already set */ -#define MARK(FOR) \ -do { \ - if (!FOR##_mark) { \ - FOR##_mark = p; \ - } \ -} while (0) - -/* Don't allow the total size of the HTTP headers (including the status - * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect - * embedders against denial-of-service attacks where the attacker feeds - * us a never-ending header that the embedder keeps buffering. - * - * This check is arguably the responsibility of embedders but we're doing - * it on the embedder's behalf because most won't bother and this way we - * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger - * than any reasonable request or response so this should never affect - * day-to-day operation. - */ -#define COUNT_HEADER_SIZE(V) \ -do { \ - parser->nread += (V); \ - if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ - SET_ERRNO(HPE_HEADER_OVERFLOW); \ - goto error; \ - } \ -} while (0) - - -#define PROXY_CONNECTION "proxy-connection" -#define CONNECTION "connection" -#define CONTENT_LENGTH "content-length" -#define TRANSFER_ENCODING "transfer-encoding" -#define UPGRADE "upgrade" -#define CHUNKED "chunked" -#define KEEP_ALIVE "keep-alive" -#define CLOSE "close" - - -static const char *method_strings[] = - { -#define XX(num, name, string) #string, - HTTP_METHOD_MAP(XX) -#undef XX - }; - - -/* Tokens as defined by rfc 2616. Also lowercases them. - * token = 1* - * separators = "(" | ")" | "<" | ">" | "@" - * | "," | ";" | ":" | "\" | <"> - * | "/" | "[" | "]" | "?" | "=" - * | "{" | "}" | SP | HT - */ -static const char tokens[256] = { -/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ - 0, '!', 0, '#', '$', '%', '&', '\'', -/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ - 0, 0, '*', '+', 0, '-', '.', 0, -/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ - '0', '1', '2', '3', '4', '5', '6', '7', -/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ - '8', '9', 0, 0, 0, 0, 0, 0, -/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ - 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', -/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ - 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', -/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', -/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ - 'x', 'y', 'z', 0, 0, 0, '^', '_', -/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ - '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', -/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ - 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', -/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', -/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ - 'x', 'y', 'z', 0, '|', 0, '~', 0 }; - - -static const int8_t unhex[256] = - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 - ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - }; - - -#if HTTP_PARSER_STRICT -# define T(v) 0 -#else -# define T(v) v -#endif - - -static const uint8_t normal_url_char[32] = { -/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, -/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ - 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, -/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, -/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ - 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, -/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ - 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, -/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, -/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, -/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ - 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; - -#undef T - -enum state - { s_dead = 1 /* important that this is > 0 */ - - , s_start_req_or_res - , s_res_or_resp_H - , s_start_res - , s_res_H - , s_res_HT - , s_res_HTT - , s_res_HTTP - , s_res_first_http_major - , s_res_http_major - , s_res_first_http_minor - , s_res_http_minor - , s_res_first_status_code - , s_res_status_code - , s_res_status_start - , s_res_status - , s_res_line_almost_done - - , s_start_req - - , s_req_method - , s_req_spaces_before_url - , s_req_schema - , s_req_schema_slash - , s_req_schema_slash_slash - , s_req_server_start - , s_req_server - , s_req_server_with_at - , s_req_path - , s_req_query_string_start - , s_req_query_string - , s_req_fragment_start - , s_req_fragment - , s_req_http_start - , s_req_http_H - , s_req_http_HT - , s_req_http_HTT - , s_req_http_HTTP - , s_req_first_http_major - , s_req_http_major - , s_req_first_http_minor - , s_req_http_minor - , s_req_line_almost_done - - , s_header_field_start - , s_header_field - , s_header_value_discard_ws - , s_header_value_discard_ws_almost_done - , s_header_value_discard_lws - , s_header_value_start - , s_header_value - , s_header_value_lws - - , s_header_almost_done - - , s_chunk_size_start - , s_chunk_size - , s_chunk_parameters - , s_chunk_size_almost_done - - , s_headers_almost_done - , s_headers_done - - /* Important: 's_headers_done' must be the last 'header' state. All - * states beyond this must be 'body' states. It is used for overflow - * checking. See the PARSING_HEADER() macro. - */ - - , s_chunk_data - , s_chunk_data_almost_done - , s_chunk_data_done - - , s_body_identity - , s_body_identity_eof - - , s_message_done - }; - - -#define PARSING_HEADER(state) (state <= s_headers_done) - - -enum header_states - { h_general = 0 - , h_C - , h_CO - , h_CON - - , h_matching_connection - , h_matching_proxy_connection - , h_matching_content_length - , h_matching_transfer_encoding - , h_matching_upgrade - - , h_connection - , h_content_length - , h_transfer_encoding - , h_upgrade - - , h_matching_transfer_encoding_chunked - , h_matching_connection_token_start - , h_matching_connection_keep_alive - , h_matching_connection_close - , h_matching_connection_upgrade - , h_matching_connection_token - - , h_transfer_encoding_chunked - , h_connection_keep_alive - , h_connection_close - , h_connection_upgrade - }; - -enum http_host_state - { - s_http_host_dead = 1 - , s_http_userinfo_start - , s_http_userinfo - , s_http_host_start - , s_http_host_v6_start - , s_http_host - , s_http_host_v6 - , s_http_host_v6_end - , s_http_host_port_start - , s_http_host_port -}; - -/* Macros for character classes; depends on strict-mode */ -#define CR '\r' -#define LF '\n' -#define LOWER(c) (unsigned char)(c | 0x20) -#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') -#define IS_NUM(c) ((c) >= '0' && (c) <= '9') -#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) -#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) -#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ - (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ - (c) == ')') -#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ - (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ - (c) == '$' || (c) == ',') - -#define STRICT_TOKEN(c) (tokens[(unsigned char)c]) - -#if HTTP_PARSER_STRICT -#define TOKEN(c) (tokens[(unsigned char)c]) -#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) -#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') -#else -#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) -#define IS_URL_CHAR(c) \ - (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) -#define IS_HOST_CHAR(c) \ - (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') -#endif - - -#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) - - -#if HTTP_PARSER_STRICT -# define STRICT_CHECK(cond) \ -do { \ - if (cond) { \ - SET_ERRNO(HPE_STRICT); \ - goto error; \ - } \ -} while (0) -# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) -#else -# define STRICT_CHECK(cond) -# define NEW_MESSAGE() start_state -#endif - - -/* Map errno values to strings for human-readable output */ -#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, -static struct { - const char *name; - const char *description; -} http_strerror_tab[] = { - HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) -}; -#undef HTTP_STRERROR_GEN - -int http_message_needs_eof(const http_parser *parser); - -/* Our URL parser. - * - * This is designed to be shared by http_parser_execute() for URL validation, - * hence it has a state transition + byte-for-byte interface. In addition, it - * is meant to be embedded in http_parser_parse_url(), which does the dirty - * work of turning state transitions URL components for its API. - * - * This function should only be invoked with non-space characters. It is - * assumed that the caller cares about (and can detect) the transition between - * URL and non-URL states by looking for these. - */ -static enum state -parse_url_char(enum state s, const char ch) -{ - if (ch == ' ' || ch == '\r' || ch == '\n') { - return s_dead; - } - -#if HTTP_PARSER_STRICT - if (ch == '\t' || ch == '\f') { - return s_dead; - } -#endif - - switch (s) { - case s_req_spaces_before_url: - /* Proxied requests are followed by scheme of an absolute URI (alpha). - * All methods except CONNECT are followed by '/' or '*'. - */ - - if (ch == '/' || ch == '*') { - return s_req_path; - } - - if (IS_ALPHA(ch)) { - return s_req_schema; - } - - break; - - case s_req_schema: - if (IS_ALPHA(ch)) { - return s; - } - - if (ch == ':') { - return s_req_schema_slash; - } - - break; - - case s_req_schema_slash: - if (ch == '/') { - return s_req_schema_slash_slash; - } - - break; - - case s_req_schema_slash_slash: - if (ch == '/') { - return s_req_server_start; - } - - break; - - case s_req_server_with_at: - if (ch == '@') { - return s_dead; - } - - /* FALLTHROUGH */ - case s_req_server_start: - case s_req_server: - if (ch == '/') { - return s_req_path; - } - - if (ch == '?') { - return s_req_query_string_start; - } - - if (ch == '@') { - return s_req_server_with_at; - } - - if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { - return s_req_server; - } - - break; - - case s_req_path: - if (IS_URL_CHAR(ch)) { - return s; - } - - switch (ch) { - case '?': - return s_req_query_string_start; - - case '#': - return s_req_fragment_start; - } - - break; - - case s_req_query_string_start: - case s_req_query_string: - if (IS_URL_CHAR(ch)) { - return s_req_query_string; - } - - switch (ch) { - case '?': - /* allow extra '?' in query string */ - return s_req_query_string; - - case '#': - return s_req_fragment_start; - } - - break; - - case s_req_fragment_start: - if (IS_URL_CHAR(ch)) { - return s_req_fragment; - } - - switch (ch) { - case '?': - return s_req_fragment; - - case '#': - return s; - } - - break; - - case s_req_fragment: - if (IS_URL_CHAR(ch)) { - return s; - } - - switch (ch) { - case '?': - case '#': - return s; - } - - break; - - default: - break; - } - - /* We should never fall out of the switch above unless there's an error */ - return s_dead; -} - -size_t http_parser_execute (http_parser *parser, - const http_parser_settings *settings, - const char *data, - size_t len) -{ - char c, ch; - int8_t unhex_val; - const char *p = data; - const char *header_field_mark = 0; - const char *header_value_mark = 0; - const char *url_mark = 0; - const char *body_mark = 0; - const char *status_mark = 0; - enum state p_state = (enum state) parser->state; - - /* We're in an error state. Don't bother doing anything. */ - if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { - return 0; - } - - if (len == 0) { - switch (CURRENT_STATE()) { - case s_body_identity_eof: - /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if - * we got paused. - */ - CALLBACK_NOTIFY_NOADVANCE(message_complete); - return 0; - - case s_dead: - case s_start_req_or_res: - case s_start_res: - case s_start_req: - return 0; - - default: - SET_ERRNO(HPE_INVALID_EOF_STATE); - return 1; - } - } - - - if (CURRENT_STATE() == s_header_field) - header_field_mark = data; - if (CURRENT_STATE() == s_header_value) - header_value_mark = data; - switch (CURRENT_STATE()) { - case s_req_path: - case s_req_schema: - case s_req_schema_slash: - case s_req_schema_slash_slash: - case s_req_server_start: - case s_req_server: - case s_req_server_with_at: - case s_req_query_string_start: - case s_req_query_string: - case s_req_fragment_start: - case s_req_fragment: - url_mark = data; - break; - case s_res_status: - status_mark = data; - break; - default: - break; - } - - for (p=data; p != data + len; p++) { - ch = *p; - - if (PARSING_HEADER(CURRENT_STATE())) - COUNT_HEADER_SIZE(1); - -reexecute: - switch (CURRENT_STATE()) { - - case s_dead: - /* this state is used after a 'Connection: close' message - * the parser will error out if it reads another message - */ - if (LIKELY(ch == CR || ch == LF)) - break; - - SET_ERRNO(HPE_CLOSED_CONNECTION); - goto error; - - case s_start_req_or_res: - { - if (ch == CR || ch == LF) - break; - parser->flags = 0; - parser->content_length = ULLONG_MAX; - - if (ch == 'H') { - UPDATE_STATE(s_res_or_resp_H); - - CALLBACK_NOTIFY(message_begin); - } else { - parser->type = HTTP_REQUEST; - UPDATE_STATE(s_start_req); - REEXECUTE(); - } - - break; - } - - case s_res_or_resp_H: - if (ch == 'T') { - parser->type = HTTP_RESPONSE; - UPDATE_STATE(s_res_HT); - } else { - if (UNLIKELY(ch != 'E')) { - SET_ERRNO(HPE_INVALID_CONSTANT); - goto error; - } - - parser->type = HTTP_REQUEST; - parser->method = HTTP_HEAD; - parser->index = 2; - UPDATE_STATE(s_req_method); - } - break; - - case s_start_res: - { - parser->flags = 0; - parser->content_length = ULLONG_MAX; - - switch (ch) { - case 'H': - UPDATE_STATE(s_res_H); - break; - - case CR: - case LF: - break; - - default: - SET_ERRNO(HPE_INVALID_CONSTANT); - goto error; - } - - CALLBACK_NOTIFY(message_begin); - break; - } - - case s_res_H: - STRICT_CHECK(ch != 'T'); - UPDATE_STATE(s_res_HT); - break; - - case s_res_HT: - STRICT_CHECK(ch != 'T'); - UPDATE_STATE(s_res_HTT); - break; - - case s_res_HTT: - STRICT_CHECK(ch != 'P'); - UPDATE_STATE(s_res_HTTP); - break; - - case s_res_HTTP: - STRICT_CHECK(ch != '/'); - UPDATE_STATE(s_res_first_http_major); - break; - - case s_res_first_http_major: - if (UNLIKELY(ch < '0' || ch > '9')) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_major = ch - '0'; - UPDATE_STATE(s_res_http_major); - break; - - /* major HTTP version or dot */ - case s_res_http_major: - { - if (ch == '.') { - UPDATE_STATE(s_res_first_http_minor); - break; - } - - if (!IS_NUM(ch)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_major *= 10; - parser->http_major += ch - '0'; - - if (UNLIKELY(parser->http_major > 999)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - break; - } - - /* first digit of minor HTTP version */ - case s_res_first_http_minor: - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_minor = ch - '0'; - UPDATE_STATE(s_res_http_minor); - break; - - /* minor HTTP version or end of request line */ - case s_res_http_minor: - { - if (ch == ' ') { - UPDATE_STATE(s_res_first_status_code); - break; - } - - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_minor *= 10; - parser->http_minor += ch - '0'; - - if (UNLIKELY(parser->http_minor > 999)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - break; - } - - case s_res_first_status_code: - { - if (!IS_NUM(ch)) { - if (ch == ' ') { - break; - } - - SET_ERRNO(HPE_INVALID_STATUS); - goto error; - } - parser->status_code = ch - '0'; - UPDATE_STATE(s_res_status_code); - break; - } - - case s_res_status_code: - { - if (!IS_NUM(ch)) { - switch (ch) { - case ' ': - UPDATE_STATE(s_res_status_start); - break; - case CR: - UPDATE_STATE(s_res_line_almost_done); - break; - case LF: - UPDATE_STATE(s_header_field_start); - break; - default: - SET_ERRNO(HPE_INVALID_STATUS); - goto error; - } - break; - } - - parser->status_code *= 10; - parser->status_code += ch - '0'; - - if (UNLIKELY(parser->status_code > 999)) { - SET_ERRNO(HPE_INVALID_STATUS); - goto error; - } - - break; - } - - case s_res_status_start: - { - if (ch == CR) { - UPDATE_STATE(s_res_line_almost_done); - break; - } - - if (ch == LF) { - UPDATE_STATE(s_header_field_start); - break; - } - - MARK(status); - UPDATE_STATE(s_res_status); - parser->index = 0; - break; - } - - case s_res_status: - if (ch == CR) { - UPDATE_STATE(s_res_line_almost_done); - CALLBACK_DATA(status); - break; - } - - if (ch == LF) { - UPDATE_STATE(s_header_field_start); - CALLBACK_DATA(status); - break; - } - - break; - - case s_res_line_almost_done: - STRICT_CHECK(ch != LF); - UPDATE_STATE(s_header_field_start); - break; - - case s_start_req: - { - if (ch == CR || ch == LF) - break; - parser->flags = 0; - parser->content_length = ULLONG_MAX; - - if (UNLIKELY(!IS_ALPHA(ch))) { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - - parser->method = (enum http_method) 0; - parser->index = 1; - switch (ch) { - case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; - case 'D': parser->method = HTTP_DELETE; break; - case 'G': parser->method = HTTP_GET; break; - case 'H': parser->method = HTTP_HEAD; break; - case 'L': parser->method = HTTP_LOCK; break; - case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; - case 'N': parser->method = HTTP_NOTIFY; break; - case 'O': parser->method = HTTP_OPTIONS; break; - case 'P': parser->method = HTTP_POST; - /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ - break; - case 'R': parser->method = HTTP_REPORT; break; - case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; - case 'T': parser->method = HTTP_TRACE; break; - case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; - default: - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - UPDATE_STATE(s_req_method); - - CALLBACK_NOTIFY(message_begin); - - break; - } - - case s_req_method: - { - const char *matcher; - if (UNLIKELY(ch == '\0')) { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - - matcher = method_strings[parser->method]; - if (ch == ' ' && matcher[parser->index] == '\0') { - UPDATE_STATE(s_req_spaces_before_url); - } else if (ch == matcher[parser->index]) { - ; /* nada */ - } else if (parser->method == HTTP_CONNECT) { - if (parser->index == 1 && ch == 'H') { - parser->method = HTTP_CHECKOUT; - } else if (parser->index == 2 && ch == 'P') { - parser->method = HTTP_COPY; - } else { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - } else if (parser->method == HTTP_MKCOL) { - if (parser->index == 1 && ch == 'O') { - parser->method = HTTP_MOVE; - } else if (parser->index == 1 && ch == 'E') { - parser->method = HTTP_MERGE; - } else if (parser->index == 1 && ch == '-') { - parser->method = HTTP_MSEARCH; - } else if (parser->index == 2 && ch == 'A') { - parser->method = HTTP_MKACTIVITY; - } else if (parser->index == 3 && ch == 'A') { - parser->method = HTTP_MKCALENDAR; - } else { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - } else if (parser->method == HTTP_SUBSCRIBE) { - if (parser->index == 1 && ch == 'E') { - parser->method = HTTP_SEARCH; - } else { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - } else if (parser->index == 1 && parser->method == HTTP_POST) { - if (ch == 'R') { - parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ - } else if (ch == 'U') { - parser->method = HTTP_PUT; /* or HTTP_PURGE */ - } else if (ch == 'A') { - parser->method = HTTP_PATCH; - } else { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - } else if (parser->index == 2) { - if (parser->method == HTTP_PUT) { - if (ch == 'R') { - parser->method = HTTP_PURGE; - } else { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - } else if (parser->method == HTTP_UNLOCK) { - if (ch == 'S') { - parser->method = HTTP_UNSUBSCRIBE; - } else { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - } else { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { - parser->method = HTTP_PROPPATCH; - } else { - SET_ERRNO(HPE_INVALID_METHOD); - goto error; - } - - ++parser->index; - break; - } - - case s_req_spaces_before_url: - { - if (ch == ' ') break; - - MARK(url); - if (parser->method == HTTP_CONNECT) { - UPDATE_STATE(s_req_server_start); - } - - UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); - if (UNLIKELY(CURRENT_STATE() == s_dead)) { - SET_ERRNO(HPE_INVALID_URL); - goto error; - } - - break; - } - - case s_req_schema: - case s_req_schema_slash: - case s_req_schema_slash_slash: - case s_req_server_start: - { - switch (ch) { - /* No whitespace allowed here */ - case ' ': - case CR: - case LF: - SET_ERRNO(HPE_INVALID_URL); - goto error; - default: - UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); - if (UNLIKELY(CURRENT_STATE() == s_dead)) { - SET_ERRNO(HPE_INVALID_URL); - goto error; - } - } - - break; - } - - case s_req_server: - case s_req_server_with_at: - case s_req_path: - case s_req_query_string_start: - case s_req_query_string: - case s_req_fragment_start: - case s_req_fragment: - { - switch (ch) { - case ' ': - UPDATE_STATE(s_req_http_start); - CALLBACK_DATA(url); - break; - case CR: - case LF: - parser->http_major = 0; - parser->http_minor = 9; - UPDATE_STATE((ch == CR) ? - s_req_line_almost_done : - s_header_field_start); - CALLBACK_DATA(url); - break; - default: - UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); - if (UNLIKELY(CURRENT_STATE() == s_dead)) { - SET_ERRNO(HPE_INVALID_URL); - goto error; - } - } - break; - } - - case s_req_http_start: - switch (ch) { - case 'H': - UPDATE_STATE(s_req_http_H); - break; - case ' ': - break; - default: - SET_ERRNO(HPE_INVALID_CONSTANT); - goto error; - } - break; - - case s_req_http_H: - STRICT_CHECK(ch != 'T'); - UPDATE_STATE(s_req_http_HT); - break; - - case s_req_http_HT: - STRICT_CHECK(ch != 'T'); - UPDATE_STATE(s_req_http_HTT); - break; - - case s_req_http_HTT: - STRICT_CHECK(ch != 'P'); - UPDATE_STATE(s_req_http_HTTP); - break; - - case s_req_http_HTTP: - STRICT_CHECK(ch != '/'); - UPDATE_STATE(s_req_first_http_major); - break; - - /* first digit of major HTTP version */ - case s_req_first_http_major: - if (UNLIKELY(ch < '1' || ch > '9')) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_major = ch - '0'; - UPDATE_STATE(s_req_http_major); - break; - - /* major HTTP version or dot */ - case s_req_http_major: - { - if (ch == '.') { - UPDATE_STATE(s_req_first_http_minor); - break; - } - - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_major *= 10; - parser->http_major += ch - '0'; - - if (UNLIKELY(parser->http_major > 999)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - break; - } - - /* first digit of minor HTTP version */ - case s_req_first_http_minor: - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_minor = ch - '0'; - UPDATE_STATE(s_req_http_minor); - break; - - /* minor HTTP version or end of request line */ - case s_req_http_minor: - { - if (ch == CR) { - UPDATE_STATE(s_req_line_almost_done); - break; - } - - if (ch == LF) { - UPDATE_STATE(s_header_field_start); - break; - } - - /* XXX allow spaces after digit? */ - - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_minor *= 10; - parser->http_minor += ch - '0'; - - if (UNLIKELY(parser->http_minor > 999)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - break; - } - - /* end of request line */ - case s_req_line_almost_done: - { - if (UNLIKELY(ch != LF)) { - SET_ERRNO(HPE_LF_EXPECTED); - goto error; - } - - UPDATE_STATE(s_header_field_start); - break; - } - - case s_header_field_start: - { - if (ch == CR) { - UPDATE_STATE(s_headers_almost_done); - break; - } - - if (ch == LF) { - /* they might be just sending \n instead of \r\n so this would be - * the second \n to denote the end of headers*/ - UPDATE_STATE(s_headers_almost_done); - REEXECUTE(); - } - - c = TOKEN(ch); - - if (UNLIKELY(!c)) { - SET_ERRNO(HPE_INVALID_HEADER_TOKEN); - goto error; - } - - MARK(header_field); - - parser->index = 0; - UPDATE_STATE(s_header_field); - - switch (c) { - case 'c': - parser->header_state = h_C; - break; - - case 'p': - parser->header_state = h_matching_proxy_connection; - break; - - case 't': - parser->header_state = h_matching_transfer_encoding; - break; - - case 'u': - parser->header_state = h_matching_upgrade; - break; - - default: - parser->header_state = h_general; - break; - } - break; - } - - case s_header_field: - { - const char* start = p; - for (; p != data + len; p++) { - ch = *p; - c = TOKEN(ch); - - if (!c) - break; - - switch (parser->header_state) { - case h_general: - break; - - case h_C: - parser->index++; - parser->header_state = (c == 'o' ? h_CO : h_general); - break; - - case h_CO: - parser->index++; - parser->header_state = (c == 'n' ? h_CON : h_general); - break; - - case h_CON: - parser->index++; - switch (c) { - case 'n': - parser->header_state = h_matching_connection; - break; - case 't': - parser->header_state = h_matching_content_length; - break; - default: - parser->header_state = h_general; - break; - } - break; - - /* connection */ - - case h_matching_connection: - parser->index++; - if (parser->index > sizeof(CONNECTION)-1 - || c != CONNECTION[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(CONNECTION)-2) { - parser->header_state = h_connection; - } - break; - - /* proxy-connection */ - - case h_matching_proxy_connection: - parser->index++; - if (parser->index > sizeof(PROXY_CONNECTION)-1 - || c != PROXY_CONNECTION[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { - parser->header_state = h_connection; - } - break; - - /* content-length */ - - case h_matching_content_length: - parser->index++; - if (parser->index > sizeof(CONTENT_LENGTH)-1 - || c != CONTENT_LENGTH[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { - parser->header_state = h_content_length; - } - break; - - /* transfer-encoding */ - - case h_matching_transfer_encoding: - parser->index++; - if (parser->index > sizeof(TRANSFER_ENCODING)-1 - || c != TRANSFER_ENCODING[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { - parser->header_state = h_transfer_encoding; - } - break; - - /* upgrade */ - - case h_matching_upgrade: - parser->index++; - if (parser->index > sizeof(UPGRADE)-1 - || c != UPGRADE[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(UPGRADE)-2) { - parser->header_state = h_upgrade; - } - break; - - case h_connection: - case h_content_length: - case h_transfer_encoding: - case h_upgrade: - if (ch != ' ') parser->header_state = h_general; - break; - - default: - assert(0 && "Unknown header_state"); - break; - } - } - - COUNT_HEADER_SIZE(p - start); - - if (p == data + len) { - --p; - break; - } - - if (ch == ':') { - UPDATE_STATE(s_header_value_discard_ws); - CALLBACK_DATA(header_field); - break; - } - - SET_ERRNO(HPE_INVALID_HEADER_TOKEN); - goto error; - } - - case s_header_value_discard_ws: - if (ch == ' ' || ch == '\t') break; - - if (ch == CR) { - UPDATE_STATE(s_header_value_discard_ws_almost_done); - break; - } - - if (ch == LF) { - UPDATE_STATE(s_header_value_discard_lws); - break; - } - - /* FALLTHROUGH */ - - case s_header_value_start: - { - MARK(header_value); - - UPDATE_STATE(s_header_value); - parser->index = 0; - - c = LOWER(ch); - - switch (parser->header_state) { - case h_upgrade: - parser->flags |= F_UPGRADE; - parser->header_state = h_general; - break; - - case h_transfer_encoding: - /* looking for 'Transfer-Encoding: chunked' */ - if ('c' == c) { - parser->header_state = h_matching_transfer_encoding_chunked; - } else { - parser->header_state = h_general; - } - break; - - case h_content_length: - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - goto error; - } - - parser->content_length = ch - '0'; - break; - - case h_connection: - /* looking for 'Connection: keep-alive' */ - if (c == 'k') { - parser->header_state = h_matching_connection_keep_alive; - /* looking for 'Connection: close' */ - } else if (c == 'c') { - parser->header_state = h_matching_connection_close; - } else if (c == 'u') { - parser->header_state = h_matching_connection_upgrade; - } else { - parser->header_state = h_matching_connection_token; - } - break; - - /* Multi-value `Connection` header */ - case h_matching_connection_token_start: - break; - - default: - parser->header_state = h_general; - break; - } - break; - } - - case s_header_value: - { - const char* start = p; - enum header_states h_state = (enum header_states) parser->header_state; - for (; p != data + len; p++) { - ch = *p; - if (ch == CR) { - UPDATE_STATE(s_header_almost_done); - parser->header_state = h_state; - CALLBACK_DATA(header_value); - break; - } - - if (ch == LF) { - UPDATE_STATE(s_header_almost_done); - COUNT_HEADER_SIZE(p - start); - parser->header_state = h_state; - CALLBACK_DATA_NOADVANCE(header_value); - REEXECUTE(); - } - - c = LOWER(ch); - - switch (h_state) { - case h_general: - { - const char* p_cr; - const char* p_lf; - size_t limit = data + len - p; - - limit = MIN(limit, HTTP_MAX_HEADER_SIZE); - - p_cr = (const char*) memchr(p, CR, limit); - p_lf = (const char*) memchr(p, LF, limit); - if (p_cr != NULL) { - if (p_lf != NULL && p_cr >= p_lf) - p = p_lf; - else - p = p_cr; - } else if (UNLIKELY(p_lf != NULL)) { - p = p_lf; - } else { - p = data + len; - } - --p; - - break; - } - - case h_connection: - case h_transfer_encoding: - assert(0 && "Shouldn't get here."); - break; - - case h_content_length: - { - uint64_t t; - - if (ch == ' ') break; - - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - parser->header_state = h_state; - goto error; - } - - t = parser->content_length; - t *= 10; - t += ch - '0'; - - /* Overflow? Test against a conservative limit for simplicity. */ - if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - parser->header_state = h_state; - goto error; - } - - parser->content_length = t; - break; - } - - /* Transfer-Encoding: chunked */ - case h_matching_transfer_encoding_chunked: - parser->index++; - if (parser->index > sizeof(CHUNKED)-1 - || c != CHUNKED[parser->index]) { - h_state = h_general; - } else if (parser->index == sizeof(CHUNKED)-2) { - h_state = h_transfer_encoding_chunked; - } - break; - - case h_matching_connection_token_start: - /* looking for 'Connection: keep-alive' */ - if (c == 'k') { - h_state = h_matching_connection_keep_alive; - /* looking for 'Connection: close' */ - } else if (c == 'c') { - h_state = h_matching_connection_close; - } else if (c == 'u') { - h_state = h_matching_connection_upgrade; - } else if (STRICT_TOKEN(c)) { - h_state = h_matching_connection_token; - } else if (c == ' ' || c == '\t') { - /* Skip lws */ - } else { - h_state = h_general; - } - break; - - /* looking for 'Connection: keep-alive' */ - case h_matching_connection_keep_alive: - parser->index++; - if (parser->index > sizeof(KEEP_ALIVE)-1 - || c != KEEP_ALIVE[parser->index]) { - h_state = h_matching_connection_token; - } else if (parser->index == sizeof(KEEP_ALIVE)-2) { - h_state = h_connection_keep_alive; - } - break; - - /* looking for 'Connection: close' */ - case h_matching_connection_close: - parser->index++; - if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { - h_state = h_matching_connection_token; - } else if (parser->index == sizeof(CLOSE)-2) { - h_state = h_connection_close; - } - break; - - /* looking for 'Connection: upgrade' */ - case h_matching_connection_upgrade: - parser->index++; - if (parser->index > sizeof(UPGRADE) - 1 || - c != UPGRADE[parser->index]) { - h_state = h_matching_connection_token; - } else if (parser->index == sizeof(UPGRADE)-2) { - h_state = h_connection_upgrade; - } - break; - - case h_matching_connection_token: - if (ch == ',') { - h_state = h_matching_connection_token_start; - parser->index = 0; - } - break; - - case h_transfer_encoding_chunked: - if (ch != ' ') h_state = h_general; - break; - - case h_connection_keep_alive: - case h_connection_close: - case h_connection_upgrade: - if (ch == ',') { - if (h_state == h_connection_keep_alive) { - parser->flags |= F_CONNECTION_KEEP_ALIVE; - } else if (h_state == h_connection_close) { - parser->flags |= F_CONNECTION_CLOSE; - } else if (h_state == h_connection_upgrade) { - parser->flags |= F_CONNECTION_UPGRADE; - } - h_state = h_matching_connection_token_start; - parser->index = 0; - } else if (ch != ' ') { - h_state = h_matching_connection_token; - } - break; - - default: - UPDATE_STATE(s_header_value); - h_state = h_general; - break; - } - } - parser->header_state = h_state; - - COUNT_HEADER_SIZE(p - start); - - if (p == data + len) - --p; - break; - } - - case s_header_almost_done: - { - STRICT_CHECK(ch != LF); - - UPDATE_STATE(s_header_value_lws); - break; - } - - case s_header_value_lws: - { - if (ch == ' ' || ch == '\t') { - UPDATE_STATE(s_header_value_start); - REEXECUTE(); - } - - /* finished the header */ - switch (parser->header_state) { - case h_connection_keep_alive: - parser->flags |= F_CONNECTION_KEEP_ALIVE; - break; - case h_connection_close: - parser->flags |= F_CONNECTION_CLOSE; - break; - case h_transfer_encoding_chunked: - parser->flags |= F_CHUNKED; - break; - case h_connection_upgrade: - parser->flags |= F_CONNECTION_UPGRADE; - break; - default: - break; - } - - UPDATE_STATE(s_header_field_start); - REEXECUTE(); - } - - case s_header_value_discard_ws_almost_done: - { - STRICT_CHECK(ch != LF); - UPDATE_STATE(s_header_value_discard_lws); - break; - } - - case s_header_value_discard_lws: - { - if (ch == ' ' || ch == '\t') { - UPDATE_STATE(s_header_value_discard_ws); - break; - } else { - switch (parser->header_state) { - case h_connection_keep_alive: - parser->flags |= F_CONNECTION_KEEP_ALIVE; - break; - case h_connection_close: - parser->flags |= F_CONNECTION_CLOSE; - break; - case h_connection_upgrade: - parser->flags |= F_CONNECTION_UPGRADE; - break; - case h_transfer_encoding_chunked: - parser->flags |= F_CHUNKED; - break; - default: - break; - } - - /* header value was empty */ - MARK(header_value); - UPDATE_STATE(s_header_field_start); - CALLBACK_DATA_NOADVANCE(header_value); - REEXECUTE(); - } - } - - case s_headers_almost_done: - { - STRICT_CHECK(ch != LF); - - if (parser->flags & F_TRAILING) { - /* End of a chunked request */ - UPDATE_STATE(s_message_done); - CALLBACK_NOTIFY_NOADVANCE(chunk_complete); - REEXECUTE(); - } - - UPDATE_STATE(s_headers_done); - - /* Set this here so that on_headers_complete() callbacks can see it */ - parser->upgrade = - ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) == - (F_UPGRADE | F_CONNECTION_UPGRADE) || - parser->method == HTTP_CONNECT); - - /* Here we call the headers_complete callback. This is somewhat - * different than other callbacks because if the user returns 1, we - * will interpret that as saying that this message has no body. This - * is needed for the annoying case of recieving a response to a HEAD - * request. - * - * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so - * we have to simulate it by handling a change in errno below. - */ - if (settings->on_headers_complete) { - switch (settings->on_headers_complete(parser)) { - case 0: - break; - - case 1: - parser->flags |= F_SKIPBODY; - break; - - default: - SET_ERRNO(HPE_CB_headers_complete); - RETURN(p - data); /* Error */ - } - } - - if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { - RETURN(p - data); - } - - REEXECUTE(); - } - - case s_headers_done: - { - STRICT_CHECK(ch != LF); - - parser->nread = 0; - - int hasBody = parser->flags & F_CHUNKED || - (parser->content_length > 0 && parser->content_length != ULLONG_MAX); - if (parser->upgrade && (parser->method == HTTP_CONNECT || - (parser->flags & F_SKIPBODY) || !hasBody)) { - /* Exit, the rest of the message is in a different protocol. */ - UPDATE_STATE(NEW_MESSAGE()); - CALLBACK_NOTIFY(message_complete); - RETURN((p - data) + 1); - } - - if (parser->flags & F_SKIPBODY) { - UPDATE_STATE(NEW_MESSAGE()); - CALLBACK_NOTIFY(message_complete); - } else if (parser->flags & F_CHUNKED) { - /* chunked encoding - ignore Content-Length header */ - UPDATE_STATE(s_chunk_size_start); - } else { - if (parser->content_length == 0) { - /* Content-Length header given but zero: Content-Length: 0\r\n */ - UPDATE_STATE(NEW_MESSAGE()); - CALLBACK_NOTIFY(message_complete); - } else if (parser->content_length != ULLONG_MAX) { - /* Content-Length header given and non-zero */ - UPDATE_STATE(s_body_identity); - } else { - if (parser->type == HTTP_REQUEST || - !http_message_needs_eof(parser)) { - /* Assume content-length 0 - read the next */ - UPDATE_STATE(NEW_MESSAGE()); - CALLBACK_NOTIFY(message_complete); - } else { - /* Read body until EOF */ - UPDATE_STATE(s_body_identity_eof); - } - } - } - - break; - } - - case s_body_identity: - { - uint64_t to_read = MIN(parser->content_length, - (uint64_t) ((data + len) - p)); - - assert(parser->content_length != 0 - && parser->content_length != ULLONG_MAX); - - /* The difference between advancing content_length and p is because - * the latter will automaticaly advance on the next loop iteration. - * Further, if content_length ends up at 0, we want to see the last - * byte again for our message complete callback. - */ - MARK(body); - parser->content_length -= to_read; - p += to_read - 1; - - if (parser->content_length == 0) { - UPDATE_STATE(s_message_done); - - /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. - * - * The alternative to doing this is to wait for the next byte to - * trigger the data callback, just as in every other case. The - * problem with this is that this makes it difficult for the test - * harness to distinguish between complete-on-EOF and - * complete-on-length. It's not clear that this distinction is - * important for applications, but let's keep it for now. - */ - CALLBACK_DATA_(body, p - body_mark + 1, p - data); - REEXECUTE(); - } - - break; - } - - /* read until EOF */ - case s_body_identity_eof: - MARK(body); - p = data + len - 1; - - break; - - case s_message_done: - UPDATE_STATE(NEW_MESSAGE()); - CALLBACK_NOTIFY(message_complete); - if (parser->upgrade) { - /* Exit, the rest of the message is in a different protocol. */ - RETURN((p - data) + 1); - } - break; - - case s_chunk_size_start: - { - assert(parser->nread == 1); - assert(parser->flags & F_CHUNKED); - - unhex_val = unhex[(unsigned char)ch]; - if (UNLIKELY(unhex_val == -1)) { - SET_ERRNO(HPE_INVALID_CHUNK_SIZE); - goto error; - } - - parser->content_length = unhex_val; - UPDATE_STATE(s_chunk_size); - break; - } - - case s_chunk_size: - { - uint64_t t; - - assert(parser->flags & F_CHUNKED); - - if (ch == CR) { - UPDATE_STATE(s_chunk_size_almost_done); - break; - } - - unhex_val = unhex[(unsigned char)ch]; - - if (unhex_val == -1) { - if (ch == ';' || ch == ' ') { - UPDATE_STATE(s_chunk_parameters); - break; - } - - SET_ERRNO(HPE_INVALID_CHUNK_SIZE); - goto error; - } - - t = parser->content_length; - t *= 16; - t += unhex_val; - - /* Overflow? Test against a conservative limit for simplicity. */ - if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - goto error; - } - - parser->content_length = t; - break; - } - - case s_chunk_parameters: - { - assert(parser->flags & F_CHUNKED); - /* just ignore this shit. TODO check for overflow */ - if (ch == CR) { - UPDATE_STATE(s_chunk_size_almost_done); - break; - } - break; - } - - case s_chunk_size_almost_done: - { - assert(parser->flags & F_CHUNKED); - STRICT_CHECK(ch != LF); - - parser->nread = 0; - - if (parser->content_length == 0) { - parser->flags |= F_TRAILING; - UPDATE_STATE(s_header_field_start); - } else { - UPDATE_STATE(s_chunk_data); - } - CALLBACK_NOTIFY(chunk_header); - break; - } - - case s_chunk_data: - { - uint64_t to_read = MIN(parser->content_length, - (uint64_t) ((data + len) - p)); - - assert(parser->flags & F_CHUNKED); - assert(parser->content_length != 0 - && parser->content_length != ULLONG_MAX); - - /* See the explanation in s_body_identity for why the content - * length and data pointers are managed this way. - */ - MARK(body); - parser->content_length -= to_read; - p += to_read - 1; - - if (parser->content_length == 0) { - UPDATE_STATE(s_chunk_data_almost_done); - } - - break; - } - - case s_chunk_data_almost_done: - assert(parser->flags & F_CHUNKED); - assert(parser->content_length == 0); - STRICT_CHECK(ch != CR); - UPDATE_STATE(s_chunk_data_done); - CALLBACK_DATA(body); - break; - - case s_chunk_data_done: - assert(parser->flags & F_CHUNKED); - STRICT_CHECK(ch != LF); - parser->nread = 0; - UPDATE_STATE(s_chunk_size_start); - CALLBACK_NOTIFY(chunk_complete); - break; - - default: - assert(0 && "unhandled state"); - SET_ERRNO(HPE_INVALID_INTERNAL_STATE); - goto error; - } - } - - /* Run callbacks for any marks that we have leftover after we ran our of - * bytes. There should be at most one of these set, so it's OK to invoke - * them in series (unset marks will not result in callbacks). - * - * We use the NOADVANCE() variety of callbacks here because 'p' has already - * overflowed 'data' and this allows us to correct for the off-by-one that - * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' - * value that's in-bounds). - */ - - assert(((header_field_mark ? 1 : 0) + - (header_value_mark ? 1 : 0) + - (url_mark ? 1 : 0) + - (body_mark ? 1 : 0) + - (status_mark ? 1 : 0)) <= 1); - - CALLBACK_DATA_NOADVANCE(header_field); - CALLBACK_DATA_NOADVANCE(header_value); - CALLBACK_DATA_NOADVANCE(url); - CALLBACK_DATA_NOADVANCE(body); - CALLBACK_DATA_NOADVANCE(status); - - RETURN(len); - -error: - if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { - SET_ERRNO(HPE_UNKNOWN); - } - - RETURN(p - data); -} - - -/* Does the parser need to see an EOF to find the end of the message? */ -int -http_message_needs_eof (const http_parser *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; - } - - if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { - return 0; - } - - return 1; -} - - -int -http_should_keep_alive (const http_parser *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 !http_message_needs_eof(parser); -} - - -const char * -http_method_str (enum http_method m) -{ - return ELEM_AT(method_strings, m, ""); -} - - -void -http_parser_init (http_parser *parser, enum http_parser_type t) -{ - void *data = parser->data; /* preserve application data */ - memset(parser, 0, sizeof(*parser)); - parser->data = data; - parser->type = t; - parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); - parser->http_errno = HPE_OK; -} - -void -http_parser_settings_init(http_parser_settings *settings) -{ - memset(settings, 0, sizeof(*settings)); -} - -const char * -http_errno_name(enum http_errno err) { - assert(((size_t) err) < - (sizeof(http_strerror_tab) / sizeof(http_strerror_tab[0]))); - return http_strerror_tab[err].name; -} - -const char * -http_errno_description(enum http_errno err) { - assert(((size_t) err) < - (sizeof(http_strerror_tab) / sizeof(http_strerror_tab[0]))); - return http_strerror_tab[err].description; -} - -static enum http_host_state -http_parse_host_char(enum http_host_state s, const char ch) { - switch(s) { - case s_http_userinfo: - case s_http_userinfo_start: - if (ch == '@') { - return s_http_host_start; - } - - if (IS_USERINFO_CHAR(ch)) { - return s_http_userinfo; - } - break; - - case s_http_host_start: - if (ch == '[') { - return s_http_host_v6_start; - } - - if (IS_HOST_CHAR(ch)) { - return s_http_host; - } - - break; - - case s_http_host: - if (IS_HOST_CHAR(ch)) { - return s_http_host; - } - - /* FALLTHROUGH */ - case s_http_host_v6_end: - if (ch == ':') { - return s_http_host_port_start; - } - - break; - - case s_http_host_v6: - if (ch == ']') { - return s_http_host_v6_end; - } - - /* FALLTHROUGH */ - case s_http_host_v6_start: - if (IS_HEX(ch) || ch == ':' || ch == '.') { - return s_http_host_v6; - } - - break; - - case s_http_host_port: - case s_http_host_port_start: - if (IS_NUM(ch)) { - return s_http_host_port; - } - - break; - - default: - break; - } - return s_http_host_dead; -} - -static int -http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { - enum http_host_state s; - - const char *p; - size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; - - u->field_data[UF_HOST].len = 0; - - s = found_at ? s_http_userinfo_start : s_http_host_start; - - for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { - enum http_host_state new_s = http_parse_host_char(s, *p); - - if (new_s == s_http_host_dead) { - return 1; - } - - switch(new_s) { - case s_http_host: - if (s != s_http_host) { - u->field_data[UF_HOST].off = p - buf; - } - u->field_data[UF_HOST].len++; - break; - - case s_http_host_v6: - if (s != s_http_host_v6) { - u->field_data[UF_HOST].off = p - buf; - } - u->field_data[UF_HOST].len++; - break; - - case s_http_host_port: - if (s != s_http_host_port) { - u->field_data[UF_PORT].off = p - buf; - u->field_data[UF_PORT].len = 0; - u->field_set |= (1 << UF_PORT); - } - u->field_data[UF_PORT].len++; - break; - - case s_http_userinfo: - if (s != s_http_userinfo) { - u->field_data[UF_USERINFO].off = p - buf ; - u->field_data[UF_USERINFO].len = 0; - u->field_set |= (1 << UF_USERINFO); - } - u->field_data[UF_USERINFO].len++; - break; - - default: - break; - } - s = new_s; - } - - /* Make sure we don't end somewhere unexpected */ - switch (s) { - case s_http_host_start: - case s_http_host_v6_start: - case s_http_host_v6: - case s_http_host_port_start: - case s_http_userinfo: - case s_http_userinfo_start: - return 1; - default: - break; - } - - return 0; -} - -int -http_parser_parse_url(const char *buf, size_t buflen, int is_connect, - struct http_parser_url *u) -{ - enum state s; - const char *p; - enum http_parser_url_fields uf, old_uf; - int found_at = 0; - - u->port = u->field_set = 0; - s = is_connect ? s_req_server_start : s_req_spaces_before_url; - old_uf = UF_MAX; - - for (p = buf; p < buf + buflen; p++) { - s = parse_url_char(s, *p); - - /* Figure out the next field that we're operating on */ - switch (s) { - case s_dead: - return 1; - - /* Skip delimeters */ - case s_req_schema_slash: - case s_req_schema_slash_slash: - case s_req_server_start: - case s_req_query_string_start: - case s_req_fragment_start: - continue; - - case s_req_schema: - uf = UF_SCHEMA; - break; - - case s_req_server_with_at: - found_at = 1; - - /* FALLTROUGH */ - case s_req_server: - uf = UF_HOST; - break; - - case s_req_path: - uf = UF_PATH; - break; - - case s_req_query_string: - uf = UF_QUERY; - break; - - case s_req_fragment: - uf = UF_FRAGMENT; - break; - - default: - assert(!"Unexpected state"); - return 1; - } - - /* Nothing's changed; soldier on */ - if (uf == old_uf) { - u->field_data[uf].len++; - continue; - } - - u->field_data[uf].off = p - buf; - u->field_data[uf].len = 1; - - u->field_set |= (1 << uf); - old_uf = uf; - } - - /* host must be present if there is a schema */ - /* parsing http:///toto will fail */ - if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) { - if (http_parse_host(buf, u, found_at) != 0) { - return 1; - } - } - - /* CONNECT requests can only contain "hostname:port" */ - if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { - return 1; - } - - if (u->field_set & (1 << UF_PORT)) { - /* Don't bother with endp; we've already validated the string */ - unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); - - /* Ports have a max value of 2^16 */ - if (v > 0xffff) { - return 1; - } - - u->port = (uint16_t) v; - } - - return 0; -} - -void -http_parser_pause(http_parser *parser, int paused) { - /* Users should only be pausing/unpausing a parser that is not in an error - * state. In non-debug builds, there's not much that we can do about this - * other than ignore it. - */ - if (HTTP_PARSER_ERRNO(parser) == HPE_OK || - HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { - SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); - } else { - assert(0 && "Attempting to pause parser in error state"); - } -} - -int -http_body_is_final(const struct http_parser *parser) { - return parser->state == s_message_done; -} - -unsigned long -http_parser_version(void) { - return HTTP_PARSER_VERSION_MAJOR * 0x10000 | - HTTP_PARSER_VERSION_MINOR * 0x00100 | - HTTP_PARSER_VERSION_PATCH * 0x00001; -} diff --git a/deps/http_parser/http_parser.gyp b/deps/http_parser/http_parser.gyp deleted file mode 100644 index ef34ecaeaeab45..00000000000000 --- a/deps/http_parser/http_parser.gyp +++ /dev/null @@ -1,111 +0,0 @@ -# This file is used with the GYP meta build system. -# http://code.google.com/p/gyp/ -# To build try this: -# svn co http://gyp.googlecode.com/svn/trunk gyp -# ./gyp/gyp -f make --depth=`pwd` http_parser.gyp -# ./out/Debug/test -{ - 'target_defaults': { - 'default_configuration': 'Debug', - 'configurations': { - # TODO: hoist these out and put them somewhere common, because - # RuntimeLibrary MUST MATCH across the entire project - 'Debug': { - 'defines': [ 'DEBUG', '_DEBUG' ], - 'cflags': [ '-Wall', '-Wextra', '-O0', '-g', '-ftrapv' ], - 'msvs_settings': { - 'VCCLCompilerTool': { - 'RuntimeLibrary': 1, # static debug - }, - }, - }, - 'Release': { - 'defines': [ 'NDEBUG' ], - 'cflags': [ '-Wall', '-Wextra', '-O3' ], - 'msvs_settings': { - 'VCCLCompilerTool': { - 'RuntimeLibrary': 0, # static release - }, - }, - } - }, - 'msvs_settings': { - 'VCCLCompilerTool': { - }, - 'VCLibrarianTool': { - }, - 'VCLinkerTool': { - 'GenerateDebugInformation': 'true', - }, - }, - 'conditions': [ - ['OS == "win"', { - 'defines': [ - 'WIN32' - ], - }] - ], - }, - - 'targets': [ - { - 'target_name': 'http_parser', - 'type': 'static_library', - 'include_dirs': [ '.' ], - 'direct_dependent_settings': { - 'defines': [ 'HTTP_PARSER_STRICT=0' ], - 'include_dirs': [ '.' ], - }, - 'defines': [ 'HTTP_PARSER_STRICT=0' ], - 'sources': [ './http_parser.c', ], - 'conditions': [ - ['OS=="win"', { - 'msvs_settings': { - 'VCCLCompilerTool': { - # Compile as C++. http_parser.c is actually C99, but C++ is - # close enough in this case. - 'CompileAs': 2, - }, - }, - }] - ], - }, - - { - 'target_name': 'http_parser_strict', - 'type': 'static_library', - 'include_dirs': [ '.' ], - 'direct_dependent_settings': { - 'defines': [ 'HTTP_PARSER_STRICT=1' ], - 'include_dirs': [ '.' ], - }, - 'defines': [ 'HTTP_PARSER_STRICT=1' ], - 'sources': [ './http_parser.c', ], - 'conditions': [ - ['OS=="win"', { - 'msvs_settings': { - 'VCCLCompilerTool': { - # Compile as C++. http_parser.c is actually C99, but C++ is - # close enough in this case. - 'CompileAs': 2, - }, - }, - }] - ], - }, - - { - 'target_name': 'test-nonstrict', - 'type': 'executable', - 'dependencies': [ 'http_parser' ], - 'sources': [ 'test.c' ] - }, - - { - 'target_name': 'test-strict', - 'type': 'executable', - 'dependencies': [ 'http_parser_strict' ], - 'sources': [ 'test.c' ] - } - ] -} diff --git a/deps/http_parser/http_parser.h b/deps/http_parser/http_parser.h deleted file mode 100644 index eb71bf99219315..00000000000000 --- a/deps/http_parser/http_parser.h +++ /dev/null @@ -1,342 +0,0 @@ -/* 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 5 -#define HTTP_PARSER_VERSION_PATCH 0 - -#include -#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) -#include -#include -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; -#else -#include -#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=). 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. - * - * 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*); - - -/* 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) \ - /* subversion */ \ - XX(16, REPORT, REPORT) \ - XX(17, MKACTIVITY, MKACTIVITY) \ - XX(18, CHECKOUT, CHECKOUT) \ - XX(19, MERGE, MERGE) \ - /* upnp */ \ - XX(20, MSEARCH, M-SEARCH) \ - XX(21, NOTIFY, NOTIFY) \ - XX(22, SUBSCRIBE, SUBSCRIBE) \ - XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \ - /* RFC-5789 */ \ - XX(24, PATCH, PATCH) \ - XX(25, PURGE, PURGE) \ - /* CalDAV */ \ - XX(26, MKCALENDAR, MKCALENDAR) \ - -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 - }; - - -/* 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(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") - - -/* 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 : 7; /* F_* values from 'flags' enum; semi-public */ - unsigned int state : 7; /* enum state from http_parser.c */ - unsigned int header_state : 8; /* enum header_state from http_parser.c */ - unsigned int index : 8; /* index into current matcher */ - - uint32_t nread; /* # bytes read in various scenarios */ - uint64_t content_length; /* # bytes in body (0 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 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 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 = 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 http_parser_version(void); - -void http_parser_init(http_parser *parser, enum http_parser_type type); - - -/* Initialize http_parser_settings members to 0 - */ -void http_parser_settings_init(http_parser_settings *settings); - - -/* Executes the parser. Returns number of parsed bytes. Sets - * `parser->http_errno` on error. */ -size_t http_parser_execute(http_parser *parser, - const http_parser_settings *settings, - const char *data, - size_t len); - - -/* If 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 http_should_keep_alive(const http_parser *parser); - -/* Returns a string version of the HTTP method. */ -const char *http_method_str(enum http_method m); - -/* Return a string name of the given error */ -const char *http_errno_name(enum http_errno err); - -/* Return a string description of the given error */ -const char *http_errno_description(enum http_errno err); - -/* Parse a URL; return nonzero on failure */ -int 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 http_parser_pause(http_parser *parser, int paused); - -/* Checks if this is the final chunk of the body. */ -int http_body_is_final(const http_parser *parser); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/deps/http_parser/test.c b/deps/http_parser/test.c deleted file mode 100644 index 4c00571eba60bc..00000000000000 --- a/deps/http_parser/test.c +++ /dev/null @@ -1,3852 +0,0 @@ -/* 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. - */ -#include "http_parser.h" -#include -#include -#include -#include /* rand */ -#include -#include - -#if defined(__APPLE__) -# undef strlcat -# undef strlncpy -# undef strlcpy -#endif /* defined(__APPLE__) */ - -#undef TRUE -#define TRUE 1 -#undef FALSE -#define FALSE 0 - -#define MAX_HEADERS 13 -#define MAX_ELEMENT_SIZE 2048 -#define MAX_CHUNKS 16 - -#define MIN(a,b) ((a) < (b) ? (a) : (b)) - -static http_parser *parser; - -struct message { - const char *name; // for debugging purposes - const char *raw; - enum http_parser_type type; - enum http_method method; - int status_code; - char response_status[MAX_ELEMENT_SIZE]; - char request_path[MAX_ELEMENT_SIZE]; - char request_url[MAX_ELEMENT_SIZE]; - char fragment[MAX_ELEMENT_SIZE]; - char query_string[MAX_ELEMENT_SIZE]; - char body[MAX_ELEMENT_SIZE]; - size_t body_size; - const char *host; - const char *userinfo; - uint16_t port; - int num_headers; - enum { NONE=0, FIELD, VALUE } last_header_element; - char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE]; - int should_keep_alive; - - int num_chunks; - int num_chunks_complete; - int chunk_lengths[MAX_CHUNKS]; - - const char *upgrade; // upgraded body - - unsigned short http_major; - unsigned short http_minor; - - int message_begin_cb_called; - int headers_complete_cb_called; - int message_complete_cb_called; - int message_complete_on_eof; - int body_is_final; -}; - -static int currently_parsing_eof; - -static struct message messages[5]; -static int num_messages; -static http_parser_settings *current_pause_parser; - -/* * R E Q U E S T S * */ -const struct message requests[] = -#define CURL_GET 0 -{ {.name= "curl get" - ,.type= HTTP_REQUEST - ,.raw= "GET /test HTTP/1.1\r\n" - "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n" - "Host: 0.0.0.0=5000\r\n" - "Accept: */*\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/test" - ,.request_url= "/test" - ,.num_headers= 3 - ,.headers= - { { "User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" } - , { "Host", "0.0.0.0=5000" } - , { "Accept", "*/*" } - } - ,.body= "" - } - -#define FIREFOX_GET 1 -, {.name= "firefox get" - ,.type= HTTP_REQUEST - ,.raw= "GET /favicon.ico HTTP/1.1\r\n" - "Host: 0.0.0.0=5000\r\n" - "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n" - "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" - "Accept-Language: en-us,en;q=0.5\r\n" - "Accept-Encoding: gzip,deflate\r\n" - "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" - "Keep-Alive: 300\r\n" - "Connection: keep-alive\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/favicon.ico" - ,.request_url= "/favicon.ico" - ,.num_headers= 8 - ,.headers= - { { "Host", "0.0.0.0=5000" } - , { "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" } - , { "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } - , { "Accept-Language", "en-us,en;q=0.5" } - , { "Accept-Encoding", "gzip,deflate" } - , { "Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7" } - , { "Keep-Alive", "300" } - , { "Connection", "keep-alive" } - } - ,.body= "" - } - -#define DUMBFUCK 2 -, {.name= "dumbfuck" - ,.type= HTTP_REQUEST - ,.raw= "GET /dumbfuck HTTP/1.1\r\n" - "aaaaaaaaaaaaa:++++++++++\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/dumbfuck" - ,.request_url= "/dumbfuck" - ,.num_headers= 1 - ,.headers= - { { "aaaaaaaaaaaaa", "++++++++++" } - } - ,.body= "" - } - -#define FRAGMENT_IN_URI 3 -, {.name= "fragment in url" - ,.type= HTTP_REQUEST - ,.raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "page=1" - ,.fragment= "posts-17408" - ,.request_path= "/forums/1/topics/2375" - /* XXX request url does include fragment? */ - ,.request_url= "/forums/1/topics/2375?page=1#posts-17408" - ,.num_headers= 0 - ,.body= "" - } - -#define GET_NO_HEADERS_NO_BODY 4 -, {.name= "get no headers no body" - ,.type= HTTP_REQUEST - ,.raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE /* would need Connection: close */ - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/get_no_headers_no_body/world" - ,.request_url= "/get_no_headers_no_body/world" - ,.num_headers= 0 - ,.body= "" - } - -#define GET_ONE_HEADER_NO_BODY 5 -, {.name= "get one header no body" - ,.type= HTTP_REQUEST - ,.raw= "GET /get_one_header_no_body HTTP/1.1\r\n" - "Accept: */*\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE /* would need Connection: close */ - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/get_one_header_no_body" - ,.request_url= "/get_one_header_no_body" - ,.num_headers= 1 - ,.headers= - { { "Accept" , "*/*" } - } - ,.body= "" - } - -#define GET_FUNKY_CONTENT_LENGTH 6 -, {.name= "get funky content length body hello" - ,.type= HTTP_REQUEST - ,.raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n" - "conTENT-Length: 5\r\n" - "\r\n" - "HELLO" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/get_funky_content_length_body_hello" - ,.request_url= "/get_funky_content_length_body_hello" - ,.num_headers= 1 - ,.headers= - { { "conTENT-Length" , "5" } - } - ,.body= "HELLO" - } - -#define POST_IDENTITY_BODY_WORLD 7 -, {.name= "post identity body world" - ,.type= HTTP_REQUEST - ,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n" - "Accept: */*\r\n" - "Transfer-Encoding: identity\r\n" - "Content-Length: 5\r\n" - "\r\n" - "World" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.query_string= "q=search" - ,.fragment= "hey" - ,.request_path= "/post_identity_body_world" - ,.request_url= "/post_identity_body_world?q=search#hey" - ,.num_headers= 3 - ,.headers= - { { "Accept", "*/*" } - , { "Transfer-Encoding", "identity" } - , { "Content-Length", "5" } - } - ,.body= "World" - } - -#define POST_CHUNKED_ALL_YOUR_BASE 8 -, {.name= "post - chunked body: all your base are belong to us" - ,.type= HTTP_REQUEST - ,.raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "1e\r\nall your base are belong to us\r\n" - "0\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/post_chunked_all_your_base" - ,.request_url= "/post_chunked_all_your_base" - ,.num_headers= 1 - ,.headers= - { { "Transfer-Encoding" , "chunked" } - } - ,.body= "all your base are belong to us" - ,.num_chunks_complete= 2 - ,.chunk_lengths= { 0x1e } - } - -#define TWO_CHUNKS_MULT_ZERO_END 9 -, {.name= "two chunks ; triple zero ending" - ,.type= HTTP_REQUEST - ,.raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "5\r\nhello\r\n" - "6\r\n world\r\n" - "000\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/two_chunks_mult_zero_end" - ,.request_url= "/two_chunks_mult_zero_end" - ,.num_headers= 1 - ,.headers= - { { "Transfer-Encoding", "chunked" } - } - ,.body= "hello world" - ,.num_chunks_complete= 3 - ,.chunk_lengths= { 5, 6 } - } - -#define CHUNKED_W_TRAILING_HEADERS 10 -, {.name= "chunked with trailing headers. blech." - ,.type= HTTP_REQUEST - ,.raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "5\r\nhello\r\n" - "6\r\n world\r\n" - "0\r\n" - "Vary: *\r\n" - "Content-Type: text/plain\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/chunked_w_trailing_headers" - ,.request_url= "/chunked_w_trailing_headers" - ,.num_headers= 3 - ,.headers= - { { "Transfer-Encoding", "chunked" } - , { "Vary", "*" } - , { "Content-Type", "text/plain" } - } - ,.body= "hello world" - ,.num_chunks_complete= 3 - ,.chunk_lengths= { 5, 6 } - } - -#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11 -, {.name= "with bullshit after the length" - ,.type= HTTP_REQUEST - ,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n" - "6; blahblah; blah\r\n world\r\n" - "0\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/chunked_w_bullshit_after_length" - ,.request_url= "/chunked_w_bullshit_after_length" - ,.num_headers= 1 - ,.headers= - { { "Transfer-Encoding", "chunked" } - } - ,.body= "hello world" - ,.num_chunks_complete= 3 - ,.chunk_lengths= { 5, 6 } - } - -#define WITH_QUOTES 12 -, {.name= "with quotes" - ,.type= HTTP_REQUEST - ,.raw= "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "foo=\"bar\"" - ,.fragment= "" - ,.request_path= "/with_\"stupid\"_quotes" - ,.request_url= "/with_\"stupid\"_quotes?foo=\"bar\"" - ,.num_headers= 0 - ,.headers= { } - ,.body= "" - } - -#define APACHEBENCH_GET 13 -/* The server receiving this request SHOULD NOT wait for EOF - * to know that content-length == 0. - * How to represent this in a unit test? message_complete_on_eof - * Compare with NO_CONTENT_LENGTH_RESPONSE. - */ -, {.name = "apachebench get" - ,.type= HTTP_REQUEST - ,.raw= "GET /test HTTP/1.0\r\n" - "Host: 0.0.0.0:5000\r\n" - "User-Agent: ApacheBench/2.3\r\n" - "Accept: */*\r\n\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/test" - ,.request_url= "/test" - ,.num_headers= 3 - ,.headers= { { "Host", "0.0.0.0:5000" } - , { "User-Agent", "ApacheBench/2.3" } - , { "Accept", "*/*" } - } - ,.body= "" - } - -#define QUERY_URL_WITH_QUESTION_MARK_GET 14 -/* Some clients include '?' characters in query strings. - */ -, {.name = "query url with question mark" - ,.type= HTTP_REQUEST - ,.raw= "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "foo=bar?baz" - ,.fragment= "" - ,.request_path= "/test.cgi" - ,.request_url= "/test.cgi?foo=bar?baz" - ,.num_headers= 0 - ,.headers= {} - ,.body= "" - } - -#define PREFIX_NEWLINE_GET 15 -/* Some clients, especially after a POST in a keep-alive connection, - * will send an extra CRLF before the next request - */ -, {.name = "newline prefix get" - ,.type= HTTP_REQUEST - ,.raw= "\r\nGET /test HTTP/1.1\r\n\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/test" - ,.request_url= "/test" - ,.num_headers= 0 - ,.headers= { } - ,.body= "" - } - -#define UPGRADE_REQUEST 16 -, {.name = "upgrade request" - ,.type= HTTP_REQUEST - ,.raw= "GET /demo HTTP/1.1\r\n" - "Host: example.com\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" - "Sec-WebSocket-Protocol: sample\r\n" - "Upgrade: WebSocket\r\n" - "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" - "Origin: http://example.com\r\n" - "\r\n" - "Hot diggity dogg" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/demo" - ,.request_url= "/demo" - ,.num_headers= 7 - ,.upgrade="Hot diggity dogg" - ,.headers= { { "Host", "example.com" } - , { "Connection", "Upgrade" } - , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } - , { "Sec-WebSocket-Protocol", "sample" } - , { "Upgrade", "WebSocket" } - , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" } - , { "Origin", "http://example.com" } - } - ,.body= "" - } - -#define CONNECT_REQUEST 17 -, {.name = "connect request" - ,.type= HTTP_REQUEST - ,.raw= "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\n" - "User-agent: Mozilla/1.1N\r\n" - "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" - "\r\n" - "some data\r\n" - "and yet even more data" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.method= HTTP_CONNECT - ,.query_string= "" - ,.fragment= "" - ,.request_path= "" - ,.request_url= "0-home0.netscape.com:443" - ,.num_headers= 2 - ,.upgrade="some data\r\nand yet even more data" - ,.headers= { { "User-agent", "Mozilla/1.1N" } - , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } - } - ,.body= "" - } - -#define REPORT_REQ 18 -, {.name= "report request" - ,.type= HTTP_REQUEST - ,.raw= "REPORT /test HTTP/1.1\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_REPORT - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/test" - ,.request_url= "/test" - ,.num_headers= 0 - ,.headers= {} - ,.body= "" - } - -#define NO_HTTP_VERSION 19 -, {.name= "request with no http version" - ,.type= HTTP_REQUEST - ,.raw= "GET /\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 0 - ,.http_minor= 9 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/" - ,.request_url= "/" - ,.num_headers= 0 - ,.headers= {} - ,.body= "" - } - -#define MSEARCH_REQ 20 -, {.name= "m-search request" - ,.type= HTTP_REQUEST - ,.raw= "M-SEARCH * HTTP/1.1\r\n" - "HOST: 239.255.255.250:1900\r\n" - "MAN: \"ssdp:discover\"\r\n" - "ST: \"ssdp:all\"\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_MSEARCH - ,.query_string= "" - ,.fragment= "" - ,.request_path= "*" - ,.request_url= "*" - ,.num_headers= 3 - ,.headers= { { "HOST", "239.255.255.250:1900" } - , { "MAN", "\"ssdp:discover\"" } - , { "ST", "\"ssdp:all\"" } - } - ,.body= "" - } - -#define LINE_FOLDING_IN_HEADER 21 -, {.name= "line folding in header value" - ,.type= HTTP_REQUEST - ,.raw= "GET / HTTP/1.1\r\n" - "Line1: abc\r\n" - "\tdef\r\n" - " ghi\r\n" - "\t\tjkl\r\n" - " mno \r\n" - "\t \tqrs\r\n" - "Line2: \t line2\t\r\n" - "Line3:\r\n" - " line3\r\n" - "Line4: \r\n" - " \r\n" - "Connection:\r\n" - " close\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/" - ,.request_url= "/" - ,.num_headers= 5 - ,.headers= { { "Line1", "abc\tdef ghi\t\tjkl mno \t \tqrs" } - , { "Line2", "line2\t" } - , { "Line3", "line3" } - , { "Line4", "" } - , { "Connection", "close" }, - } - ,.body= "" - } - - -#define QUERY_TERMINATED_HOST 22 -, {.name= "host terminated by a query string" - ,.type= HTTP_REQUEST - ,.raw= "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "hail=all" - ,.fragment= "" - ,.request_path= "" - ,.request_url= "http://hypnotoad.org?hail=all" - ,.host= "hypnotoad.org" - ,.num_headers= 0 - ,.headers= { } - ,.body= "" - } - -#define QUERY_TERMINATED_HOSTPORT 23 -, {.name= "host:port terminated by a query string" - ,.type= HTTP_REQUEST - ,.raw= "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "hail=all" - ,.fragment= "" - ,.request_path= "" - ,.request_url= "http://hypnotoad.org:1234?hail=all" - ,.host= "hypnotoad.org" - ,.port= 1234 - ,.num_headers= 0 - ,.headers= { } - ,.body= "" - } - -#define SPACE_TERMINATED_HOSTPORT 24 -, {.name= "host:port terminated by a space" - ,.type= HTTP_REQUEST - ,.raw= "GET http://hypnotoad.org:1234 HTTP/1.1\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "" - ,.request_url= "http://hypnotoad.org:1234" - ,.host= "hypnotoad.org" - ,.port= 1234 - ,.num_headers= 0 - ,.headers= { } - ,.body= "" - } - -#define PATCH_REQ 25 -, {.name = "PATCH request" - ,.type= HTTP_REQUEST - ,.raw= "PATCH /file.txt HTTP/1.1\r\n" - "Host: www.example.com\r\n" - "Content-Type: application/example\r\n" - "If-Match: \"e0023aa4e\"\r\n" - "Content-Length: 10\r\n" - "\r\n" - "cccccccccc" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_PATCH - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/file.txt" - ,.request_url= "/file.txt" - ,.num_headers= 4 - ,.headers= { { "Host", "www.example.com" } - , { "Content-Type", "application/example" } - , { "If-Match", "\"e0023aa4e\"" } - , { "Content-Length", "10" } - } - ,.body= "cccccccccc" - } - -#define CONNECT_CAPS_REQUEST 26 -, {.name = "connect caps request" - ,.type= HTTP_REQUEST - ,.raw= "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\n" - "User-agent: Mozilla/1.1N\r\n" - "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.method= HTTP_CONNECT - ,.query_string= "" - ,.fragment= "" - ,.request_path= "" - ,.request_url= "HOME0.NETSCAPE.COM:443" - ,.num_headers= 2 - ,.upgrade="" - ,.headers= { { "User-agent", "Mozilla/1.1N" } - , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } - } - ,.body= "" - } - -#if !HTTP_PARSER_STRICT -#define UTF8_PATH_REQ 27 -, {.name= "utf-8 path request" - ,.type= HTTP_REQUEST - ,.raw= "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\n" - "Host: github.com\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "q=1" - ,.fragment= "narf" - ,.request_path= "/δ¶/δt/pope" - ,.request_url= "/δ¶/δt/pope?q=1#narf" - ,.num_headers= 1 - ,.headers= { {"Host", "github.com" } - } - ,.body= "" - } - -#define HOSTNAME_UNDERSCORE 28 -, {.name = "hostname underscore" - ,.type= HTTP_REQUEST - ,.raw= "CONNECT home_0.netscape.com:443 HTTP/1.0\r\n" - "User-agent: Mozilla/1.1N\r\n" - "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.method= HTTP_CONNECT - ,.query_string= "" - ,.fragment= "" - ,.request_path= "" - ,.request_url= "home_0.netscape.com:443" - ,.num_headers= 2 - ,.upgrade="" - ,.headers= { { "User-agent", "Mozilla/1.1N" } - , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } - } - ,.body= "" - } -#endif /* !HTTP_PARSER_STRICT */ - -/* see https://github.com/ry/http-parser/issues/47 */ -#define EAT_TRAILING_CRLF_NO_CONNECTION_CLOSE 29 -, {.name = "eat CRLF between requests, no \"Connection: close\" header" - ,.raw= "POST / HTTP/1.1\r\n" - "Host: www.example.com\r\n" - "Content-Type: application/x-www-form-urlencoded\r\n" - "Content-Length: 4\r\n" - "\r\n" - "q=42\r\n" /* note the trailing CRLF */ - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/" - ,.request_url= "/" - ,.num_headers= 3 - ,.upgrade= 0 - ,.headers= { { "Host", "www.example.com" } - , { "Content-Type", "application/x-www-form-urlencoded" } - , { "Content-Length", "4" } - } - ,.body= "q=42" - } - -/* see https://github.com/ry/http-parser/issues/47 */ -#define EAT_TRAILING_CRLF_WITH_CONNECTION_CLOSE 30 -, {.name = "eat CRLF between requests even if \"Connection: close\" is set" - ,.raw= "POST / HTTP/1.1\r\n" - "Host: www.example.com\r\n" - "Content-Type: application/x-www-form-urlencoded\r\n" - "Content-Length: 4\r\n" - "Connection: close\r\n" - "\r\n" - "q=42\r\n" /* note the trailing CRLF */ - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE /* input buffer isn't empty when on_message_complete is called */ - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/" - ,.request_url= "/" - ,.num_headers= 4 - ,.upgrade= 0 - ,.headers= { { "Host", "www.example.com" } - , { "Content-Type", "application/x-www-form-urlencoded" } - , { "Content-Length", "4" } - , { "Connection", "close" } - } - ,.body= "q=42" - } - -#define PURGE_REQ 31 -, {.name = "PURGE request" - ,.type= HTTP_REQUEST - ,.raw= "PURGE /file.txt HTTP/1.1\r\n" - "Host: www.example.com\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_PURGE - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/file.txt" - ,.request_url= "/file.txt" - ,.num_headers= 1 - ,.headers= { { "Host", "www.example.com" } } - ,.body= "" - } - -#define SEARCH_REQ 32 -, {.name = "SEARCH request" - ,.type= HTTP_REQUEST - ,.raw= "SEARCH / HTTP/1.1\r\n" - "Host: www.example.com\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_SEARCH - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/" - ,.request_url= "/" - ,.num_headers= 1 - ,.headers= { { "Host", "www.example.com" } } - ,.body= "" - } - -#define PROXY_WITH_BASIC_AUTH 33 -, {.name= "host:port and basic_auth" - ,.type= HTTP_REQUEST - ,.raw= "GET http://a%12:b!&*$@hypnotoad.org:1234/toto HTTP/1.1\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.fragment= "" - ,.request_path= "/toto" - ,.request_url= "http://a%12:b!&*$@hypnotoad.org:1234/toto" - ,.host= "hypnotoad.org" - ,.userinfo= "a%12:b!&*$" - ,.port= 1234 - ,.num_headers= 0 - ,.headers= { } - ,.body= "" - } - -#define LINE_FOLDING_IN_HEADER_WITH_LF 34 -, {.name= "line folding in header value" - ,.type= HTTP_REQUEST - ,.raw= "GET / HTTP/1.1\n" - "Line1: abc\n" - "\tdef\n" - " ghi\n" - "\t\tjkl\n" - " mno \n" - "\t \tqrs\n" - "Line2: \t line2\t\n" - "Line3:\n" - " line3\n" - "Line4: \n" - " \n" - "Connection:\n" - " close\n" - "\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/" - ,.request_url= "/" - ,.num_headers= 5 - ,.headers= { { "Line1", "abc\tdef ghi\t\tjkl mno \t \tqrs" } - , { "Line2", "line2\t" } - , { "Line3", "line3" } - , { "Line4", "" } - , { "Connection", "close" }, - } - ,.body= "" - } - -#define CONNECTION_MULTI 35 -, {.name = "multiple connection header values with folding" - ,.type= HTTP_REQUEST - ,.raw= "GET /demo HTTP/1.1\r\n" - "Host: example.com\r\n" - "Connection: Something,\r\n" - " Upgrade, ,Keep-Alive\r\n" - "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" - "Sec-WebSocket-Protocol: sample\r\n" - "Upgrade: WebSocket\r\n" - "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" - "Origin: http://example.com\r\n" - "\r\n" - "Hot diggity dogg" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/demo" - ,.request_url= "/demo" - ,.num_headers= 7 - ,.upgrade="Hot diggity dogg" - ,.headers= { { "Host", "example.com" } - , { "Connection", "Something, Upgrade, ,Keep-Alive" } - , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } - , { "Sec-WebSocket-Protocol", "sample" } - , { "Upgrade", "WebSocket" } - , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" } - , { "Origin", "http://example.com" } - } - ,.body= "" - } - -#define CONNECTION_MULTI_LWS 36 -, {.name = "multiple connection header values with folding and lws" - ,.type= HTTP_REQUEST - ,.raw= "GET /demo HTTP/1.1\r\n" - "Connection: keep-alive, upgrade\r\n" - "Upgrade: WebSocket\r\n" - "\r\n" - "Hot diggity dogg" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/demo" - ,.request_url= "/demo" - ,.num_headers= 2 - ,.upgrade="Hot diggity dogg" - ,.headers= { { "Connection", "keep-alive, upgrade" } - , { "Upgrade", "WebSocket" } - } - ,.body= "" - } - -#define CONNECTION_MULTI_LWS_CRLF 37 -, {.name = "multiple connection header values with folding and lws" - ,.type= HTTP_REQUEST - ,.raw= "GET /demo HTTP/1.1\r\n" - "Connection: keep-alive, \r\n upgrade\r\n" - "Upgrade: WebSocket\r\n" - "\r\n" - "Hot diggity dogg" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/demo" - ,.request_url= "/demo" - ,.num_headers= 2 - ,.upgrade="Hot diggity dogg" - ,.headers= { { "Connection", "keep-alive, upgrade" } - , { "Upgrade", "WebSocket" } - } - ,.body= "" - } - -#define UPGRADE_POST_REQUEST 38 -, {.name = "upgrade post request" - ,.type= HTTP_REQUEST - ,.raw= "POST /demo HTTP/1.1\r\n" - "Host: example.com\r\n" - "Connection: Upgrade\r\n" - "Upgrade: HTTP/2.0\r\n" - "Content-Length: 15\r\n" - "\r\n" - "sweet post body" - "Hot diggity dogg" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.method= HTTP_POST - ,.request_path= "/demo" - ,.request_url= "/demo" - ,.num_headers= 4 - ,.upgrade="Hot diggity dogg" - ,.headers= { { "Host", "example.com" } - , { "Connection", "Upgrade" } - , { "Upgrade", "HTTP/2.0" } - , { "Content-Length", "15" } - } - ,.body= "sweet post body" - } - -#define CONNECT_WITH_BODY_REQUEST 39 -, {.name = "connect with body request" - ,.type= HTTP_REQUEST - ,.raw= "CONNECT foo.bar.com:443 HTTP/1.0\r\n" - "User-agent: Mozilla/1.1N\r\n" - "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" - "Content-Length: 10\r\n" - "\r\n" - "blarfcicle" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.method= HTTP_CONNECT - ,.request_url= "foo.bar.com:443" - ,.num_headers= 3 - ,.upgrade="blarfcicle" - ,.headers= { { "User-agent", "Mozilla/1.1N" } - , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } - , { "Content-Length", "10" } - } - ,.body= "" - } - -, {.name= NULL } /* sentinel */ -}; - -/* * R E S P O N S E S * */ -const struct message responses[] = -#define GOOGLE_301 0 -{ {.name= "google 301" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 301 Moved Permanently\r\n" - "Location: http://www.google.com/\r\n" - "Content-Type: text/html; charset=UTF-8\r\n" - "Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n" - "Expires: Tue, 26 May 2009 11:11:49 GMT\r\n" - "X-$PrototypeBI-Version: 1.6.0.3\r\n" /* $ char in header field */ - "Cache-Control: public, max-age=2592000\r\n" - "Server: gws\r\n" - "Content-Length: 219 \r\n" - "\r\n" - "\n" - "301 Moved\n" - "

301 Moved

\n" - "The document has moved\n" - "here.\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 301 - ,.response_status= "Moved Permanently" - ,.num_headers= 8 - ,.headers= - { { "Location", "http://www.google.com/" } - , { "Content-Type", "text/html; charset=UTF-8" } - , { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" } - , { "Expires", "Tue, 26 May 2009 11:11:49 GMT" } - , { "X-$PrototypeBI-Version", "1.6.0.3" } - , { "Cache-Control", "public, max-age=2592000" } - , { "Server", "gws" } - , { "Content-Length", "219 " } - } - ,.body= "\n" - "301 Moved\n" - "

301 Moved

\n" - "The document has moved\n" - "here.\r\n" - "\r\n" - } - -#define NO_CONTENT_LENGTH_RESPONSE 1 -/* The client should wait for the server's EOF. That is, when content-length - * is not specified, and "Connection: close", the end of body is specified - * by the EOF. - * Compare with APACHEBENCH_GET - */ -, {.name= "no content-length response" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n" - "Server: Apache\r\n" - "X-Powered-By: Servlet/2.5 JSP/2.1\r\n" - "Content-Type: text/xml; charset=utf-8\r\n" - "Connection: close\r\n" - "\r\n" - "\n" - "\n" - " \n" - " \n" - " SOAP-ENV:Client\n" - " Client Error\n" - " \n" - " \n" - "" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.response_status= "OK" - ,.num_headers= 5 - ,.headers= - { { "Date", "Tue, 04 Aug 2009 07:59:32 GMT" } - , { "Server", "Apache" } - , { "X-Powered-By", "Servlet/2.5 JSP/2.1" } - , { "Content-Type", "text/xml; charset=utf-8" } - , { "Connection", "close" } - } - ,.body= "\n" - "\n" - " \n" - " \n" - " SOAP-ENV:Client\n" - " Client Error\n" - " \n" - " \n" - "" - } - -#define NO_HEADERS_NO_BODY_404 2 -, {.name= "404 no headers no body" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 404 Not Found\r\n\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 404 - ,.response_status= "Not Found" - ,.num_headers= 0 - ,.headers= {} - ,.body_size= 0 - ,.body= "" - } - -#define NO_REASON_PHRASE 3 -, {.name= "301 no response phrase" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 301\r\n\r\n" - ,.should_keep_alive = FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 301 - ,.response_status= "" - ,.num_headers= 0 - ,.headers= {} - ,.body= "" - } - -#define TRAILING_SPACE_ON_CHUNKED_BODY 4 -, {.name="200 trailing space on chunked body" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Content-Type: text/plain\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "25 \r\n" - "This is the data in the first chunk\r\n" - "\r\n" - "1C\r\n" - "and this is the second one\r\n" - "\r\n" - "0 \r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.response_status= "OK" - ,.num_headers= 2 - ,.headers= - { {"Content-Type", "text/plain" } - , {"Transfer-Encoding", "chunked" } - } - ,.body_size = 37+28 - ,.body = - "This is the data in the first chunk\r\n" - "and this is the second one\r\n" - ,.num_chunks_complete= 3 - ,.chunk_lengths= { 0x25, 0x1c } - } - -#define NO_CARRIAGE_RET 5 -, {.name="no carriage ret" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\n" - "Content-Type: text/html; charset=utf-8\n" - "Connection: close\n" - "\n" - "these headers are from http://news.ycombinator.com/" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.response_status= "OK" - ,.num_headers= 2 - ,.headers= - { {"Content-Type", "text/html; charset=utf-8" } - , {"Connection", "close" } - } - ,.body= "these headers are from http://news.ycombinator.com/" - } - -#define PROXY_CONNECTION 6 -, {.name="proxy connection" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Content-Type: text/html; charset=UTF-8\r\n" - "Content-Length: 11\r\n" - "Proxy-Connection: close\r\n" - "Date: Thu, 31 Dec 2009 20:55:48 +0000\r\n" - "\r\n" - "hello world" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.response_status= "OK" - ,.num_headers= 4 - ,.headers= - { {"Content-Type", "text/html; charset=UTF-8" } - , {"Content-Length", "11" } - , {"Proxy-Connection", "close" } - , {"Date", "Thu, 31 Dec 2009 20:55:48 +0000"} - } - ,.body= "hello world" - } - -#define UNDERSTORE_HEADER_KEY 7 - // shown by - // curl -o /dev/null -v "http://ad.doubleclick.net/pfadx/DARTSHELLCONFIGXML;dcmt=text/xml;" -, {.name="underscore header key" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Server: DCLK-AdSvr\r\n" - "Content-Type: text/xml\r\n" - "Content-Length: 0\r\n" - "DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.response_status= "OK" - ,.num_headers= 4 - ,.headers= - { {"Server", "DCLK-AdSvr" } - , {"Content-Type", "text/xml" } - , {"Content-Length", "0" } - , {"DCLK_imp", "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o" } - } - ,.body= "" - } - -#define BONJOUR_MADAME_FR 8 -/* The client should not merge two headers fields when the first one doesn't - * have a value. - */ -, {.name= "bonjourmadame.fr" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.0 301 Moved Permanently\r\n" - "Date: Thu, 03 Jun 2010 09:56:32 GMT\r\n" - "Server: Apache/2.2.3 (Red Hat)\r\n" - "Cache-Control: public\r\n" - "Pragma: \r\n" - "Location: http://www.bonjourmadame.fr/\r\n" - "Vary: Accept-Encoding\r\n" - "Content-Length: 0\r\n" - "Content-Type: text/html; charset=UTF-8\r\n" - "Connection: keep-alive\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.status_code= 301 - ,.response_status= "Moved Permanently" - ,.num_headers= 9 - ,.headers= - { { "Date", "Thu, 03 Jun 2010 09:56:32 GMT" } - , { "Server", "Apache/2.2.3 (Red Hat)" } - , { "Cache-Control", "public" } - , { "Pragma", "" } - , { "Location", "http://www.bonjourmadame.fr/" } - , { "Vary", "Accept-Encoding" } - , { "Content-Length", "0" } - , { "Content-Type", "text/html; charset=UTF-8" } - , { "Connection", "keep-alive" } - } - ,.body= "" - } - -#define RES_FIELD_UNDERSCORE 9 -/* Should handle spaces in header fields */ -, {.name= "field underscore" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Date: Tue, 28 Sep 2010 01:14:13 GMT\r\n" - "Server: Apache\r\n" - "Cache-Control: no-cache, must-revalidate\r\n" - "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" - ".et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\n" - "Vary: Accept-Encoding\r\n" - "_eep-Alive: timeout=45\r\n" /* semantic value ignored */ - "_onnection: Keep-Alive\r\n" /* semantic value ignored */ - "Transfer-Encoding: chunked\r\n" - "Content-Type: text/html\r\n" - "Connection: close\r\n" - "\r\n" - "0\r\n\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.response_status= "OK" - ,.num_headers= 11 - ,.headers= - { { "Date", "Tue, 28 Sep 2010 01:14:13 GMT" } - , { "Server", "Apache" } - , { "Cache-Control", "no-cache, must-revalidate" } - , { "Expires", "Mon, 26 Jul 1997 05:00:00 GMT" } - , { ".et-Cookie", "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com" } - , { "Vary", "Accept-Encoding" } - , { "_eep-Alive", "timeout=45" } - , { "_onnection", "Keep-Alive" } - , { "Transfer-Encoding", "chunked" } - , { "Content-Type", "text/html" } - , { "Connection", "close" } - } - ,.body= "" - ,.num_chunks_complete= 1 - ,.chunk_lengths= {} - } - -#define NON_ASCII_IN_STATUS_LINE 10 -/* Should handle non-ASCII in status line */ -, {.name= "non-ASCII in status line" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 500 Oriëntatieprobleem\r\n" - "Date: Fri, 5 Nov 2010 23:07:12 GMT+2\r\n" - "Content-Length: 0\r\n" - "Connection: close\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 500 - ,.response_status= "Oriëntatieprobleem" - ,.num_headers= 3 - ,.headers= - { { "Date", "Fri, 5 Nov 2010 23:07:12 GMT+2" } - , { "Content-Length", "0" } - , { "Connection", "close" } - } - ,.body= "" - } - -#define HTTP_VERSION_0_9 11 -/* Should handle HTTP/0.9 */ -, {.name= "http version 0.9" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/0.9 200 OK\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 0 - ,.http_minor= 9 - ,.status_code= 200 - ,.response_status= "OK" - ,.num_headers= 0 - ,.headers= - {} - ,.body= "" - } - -#define NO_CONTENT_LENGTH_NO_TRANSFER_ENCODING_RESPONSE 12 -/* The client should wait for the server's EOF. That is, when neither - * content-length nor transfer-encoding is specified, the end of body - * is specified by the EOF. - */ -, {.name= "neither content-length nor transfer-encoding response" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Content-Type: text/plain\r\n" - "\r\n" - "hello world" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.response_status= "OK" - ,.num_headers= 1 - ,.headers= - { { "Content-Type", "text/plain" } - } - ,.body= "hello world" - } - -#define NO_BODY_HTTP10_KA_200 13 -, {.name= "HTTP/1.0 with keep-alive and EOF-terminated 200 status" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.0 200 OK\r\n" - "Connection: keep-alive\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 0 - ,.status_code= 200 - ,.response_status= "OK" - ,.num_headers= 1 - ,.headers= - { { "Connection", "keep-alive" } - } - ,.body_size= 0 - ,.body= "" - } - -#define NO_BODY_HTTP10_KA_204 14 -, {.name= "HTTP/1.0 with keep-alive and a 204 status" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.0 204 No content\r\n" - "Connection: keep-alive\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.status_code= 204 - ,.response_status= "No content" - ,.num_headers= 1 - ,.headers= - { { "Connection", "keep-alive" } - } - ,.body_size= 0 - ,.body= "" - } - -#define NO_BODY_HTTP11_KA_200 15 -, {.name= "HTTP/1.1 with an EOF-terminated 200 status" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.response_status= "OK" - ,.num_headers= 0 - ,.headers={} - ,.body_size= 0 - ,.body= "" - } - -#define NO_BODY_HTTP11_KA_204 16 -, {.name= "HTTP/1.1 with a 204 status" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 204 No content\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 204 - ,.response_status= "No content" - ,.num_headers= 0 - ,.headers={} - ,.body_size= 0 - ,.body= "" - } - -#define NO_BODY_HTTP11_NOKA_204 17 -, {.name= "HTTP/1.1 with a 204 status and keep-alive disabled" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 204 No content\r\n" - "Connection: close\r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 204 - ,.response_status= "No content" - ,.num_headers= 1 - ,.headers= - { { "Connection", "close" } - } - ,.body_size= 0 - ,.body= "" - } - -#define NO_BODY_HTTP11_KA_CHUNKED_200 18 -, {.name= "HTTP/1.1 with chunked endocing and a 200 response" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "0\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.response_status= "OK" - ,.num_headers= 1 - ,.headers= - { { "Transfer-Encoding", "chunked" } - } - ,.body_size= 0 - ,.body= "" - ,.num_chunks_complete= 1 - } - -#if !HTTP_PARSER_STRICT -#define SPACE_IN_FIELD_RES 19 -/* Should handle spaces in header fields */ -, {.name= "field space" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 OK\r\n" - "Server: Microsoft-IIS/6.0\r\n" - "X-Powered-By: ASP.NET\r\n" - "en-US Content-Type: text/xml\r\n" /* this is the problem */ - "Content-Type: text/xml\r\n" - "Content-Length: 16\r\n" - "Date: Fri, 23 Jul 2010 18:45:38 GMT\r\n" - "Connection: keep-alive\r\n" - "\r\n" - "hello" /* fake body */ - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.response_status= "OK" - ,.num_headers= 7 - ,.headers= - { { "Server", "Microsoft-IIS/6.0" } - , { "X-Powered-By", "ASP.NET" } - , { "en-US Content-Type", "text/xml" } - , { "Content-Type", "text/xml" } - , { "Content-Length", "16" } - , { "Date", "Fri, 23 Jul 2010 18:45:38 GMT" } - , { "Connection", "keep-alive" } - } - ,.body= "hello" - } -#endif /* !HTTP_PARSER_STRICT */ - -#define AMAZON_COM 20 -, {.name= "amazon.com" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 301 MovedPermanently\r\n" - "Date: Wed, 15 May 2013 17:06:33 GMT\r\n" - "Server: Server\r\n" - "x-amz-id-1: 0GPHKXSJQ826RK7GZEB2\r\n" - "p3p: policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"\r\n" - "x-amz-id-2: STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD\r\n" - "Location: http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846\r\n" - "Vary: Accept-Encoding,User-Agent\r\n" - "Content-Type: text/html; charset=ISO-8859-1\r\n" - "Transfer-Encoding: chunked\r\n" - "\r\n" - "1\r\n" - "\n\r\n" - "0\r\n" - "\r\n" - ,.should_keep_alive= TRUE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 301 - ,.response_status= "MovedPermanently" - ,.num_headers= 9 - ,.headers= { { "Date", "Wed, 15 May 2013 17:06:33 GMT" } - , { "Server", "Server" } - , { "x-amz-id-1", "0GPHKXSJQ826RK7GZEB2" } - , { "p3p", "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" } - , { "x-amz-id-2", "STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD" } - , { "Location", "http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846" } - , { "Vary", "Accept-Encoding,User-Agent" } - , { "Content-Type", "text/html; charset=ISO-8859-1" } - , { "Transfer-Encoding", "chunked" } - } - ,.body= "\n" - ,.num_chunks_complete= 2 - ,.chunk_lengths= { 1 } - } - -#define EMPTY_REASON_PHRASE_AFTER_SPACE 20 -, {.name= "empty reason phrase after space" - ,.type= HTTP_RESPONSE - ,.raw= "HTTP/1.1 200 \r\n" - "\r\n" - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= TRUE - ,.http_major= 1 - ,.http_minor= 1 - ,.status_code= 200 - ,.response_status= "" - ,.num_headers= 0 - ,.headers= {} - ,.body= "" - } - -, {.name= NULL } /* sentinel */ -}; - -/* strnlen() is a POSIX.2008 addition. Can't rely on it being available so - * define it ourselves. - */ -size_t -strnlen(const char *s, size_t maxlen) -{ - const char *p; - - p = memchr(s, '\0', maxlen); - if (p == NULL) - return maxlen; - - return p - s; -} - -size_t -strlncat(char *dst, size_t len, const char *src, size_t n) -{ - size_t slen; - size_t dlen; - size_t rlen; - size_t ncpy; - - slen = strnlen(src, n); - dlen = strnlen(dst, len); - - if (dlen < len) { - rlen = len - dlen; - ncpy = slen < rlen ? slen : (rlen - 1); - memcpy(dst + dlen, src, ncpy); - dst[dlen + ncpy] = '\0'; - } - - assert(len > slen + dlen); - return slen + dlen; -} - -size_t -strlcat(char *dst, const char *src, size_t len) -{ - return strlncat(dst, len, src, (size_t) -1); -} - -size_t -strlncpy(char *dst, size_t len, const char *src, size_t n) -{ - size_t slen; - size_t ncpy; - - slen = strnlen(src, n); - - if (len > 0) { - ncpy = slen < len ? slen : (len - 1); - memcpy(dst, src, ncpy); - dst[ncpy] = '\0'; - } - - assert(len > slen); - return slen; -} - -size_t -strlcpy(char *dst, const char *src, size_t len) -{ - return strlncpy(dst, len, src, (size_t) -1); -} - -int -request_url_cb (http_parser *p, const char *buf, size_t len) -{ - assert(p == parser); - strlncat(messages[num_messages].request_url, - sizeof(messages[num_messages].request_url), - buf, - len); - return 0; -} - -int -header_field_cb (http_parser *p, const char *buf, size_t len) -{ - assert(p == parser); - struct message *m = &messages[num_messages]; - - if (m->last_header_element != FIELD) - m->num_headers++; - - strlncat(m->headers[m->num_headers-1][0], - sizeof(m->headers[m->num_headers-1][0]), - buf, - len); - - m->last_header_element = FIELD; - - return 0; -} - -int -header_value_cb (http_parser *p, const char *buf, size_t len) -{ - assert(p == parser); - struct message *m = &messages[num_messages]; - - strlncat(m->headers[m->num_headers-1][1], - sizeof(m->headers[m->num_headers-1][1]), - buf, - len); - - m->last_header_element = VALUE; - - return 0; -} - -void -check_body_is_final (const http_parser *p) -{ - if (messages[num_messages].body_is_final) { - fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 " - "on last on_body callback call " - "but it doesn't! ***\n\n"); - assert(0); - abort(); - } - messages[num_messages].body_is_final = http_body_is_final(p); -} - -int -body_cb (http_parser *p, const char *buf, size_t len) -{ - assert(p == parser); - strlncat(messages[num_messages].body, - sizeof(messages[num_messages].body), - buf, - len); - messages[num_messages].body_size += len; - check_body_is_final(p); - // printf("body_cb: '%s'\n", requests[num_messages].body); - return 0; -} - -int -count_body_cb (http_parser *p, const char *buf, size_t len) -{ - assert(p == parser); - assert(buf); - messages[num_messages].body_size += len; - check_body_is_final(p); - return 0; -} - -int -message_begin_cb (http_parser *p) -{ - assert(p == parser); - messages[num_messages].message_begin_cb_called = TRUE; - return 0; -} - -int -headers_complete_cb (http_parser *p) -{ - assert(p == parser); - messages[num_messages].method = parser->method; - messages[num_messages].status_code = parser->status_code; - messages[num_messages].http_major = parser->http_major; - messages[num_messages].http_minor = parser->http_minor; - messages[num_messages].headers_complete_cb_called = TRUE; - messages[num_messages].should_keep_alive = http_should_keep_alive(parser); - return 0; -} - -int -message_complete_cb (http_parser *p) -{ - assert(p == parser); - if (messages[num_messages].should_keep_alive != http_should_keep_alive(parser)) - { - fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same " - "value in both on_message_complete and on_headers_complete " - "but it doesn't! ***\n\n"); - assert(0); - abort(); - } - - if (messages[num_messages].body_size && - http_body_is_final(p) && - !messages[num_messages].body_is_final) - { - fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 " - "on last on_body callback call " - "but it doesn't! ***\n\n"); - assert(0); - abort(); - } - - messages[num_messages].message_complete_cb_called = TRUE; - - messages[num_messages].message_complete_on_eof = currently_parsing_eof; - - num_messages++; - return 0; -} - -int -response_status_cb (http_parser *p, const char *buf, size_t len) -{ - assert(p == parser); - strlncat(messages[num_messages].response_status, - sizeof(messages[num_messages].response_status), - buf, - len); - return 0; -} - -int -chunk_header_cb (http_parser *p) -{ - assert(p == parser); - int chunk_idx = messages[num_messages].num_chunks; - messages[num_messages].num_chunks++; - if (chunk_idx < MAX_CHUNKS) { - messages[num_messages].chunk_lengths[chunk_idx] = p->content_length; - } - - return 0; -} - -int -chunk_complete_cb (http_parser *p) -{ - assert(p == parser); - - /* Here we want to verify that each chunk_header_cb is matched by a - * chunk_complete_cb, so not only should the total number of calls to - * both callbacks be the same, but they also should be interleaved - * properly */ - assert(messages[num_messages].num_chunks == - messages[num_messages].num_chunks_complete + 1); - - messages[num_messages].num_chunks_complete++; - return 0; -} - -/* These dontcall_* callbacks exist so that we can verify that when we're - * paused, no additional callbacks are invoked */ -int -dontcall_message_begin_cb (http_parser *p) -{ - if (p) { } // gcc - fprintf(stderr, "\n\n*** on_message_begin() called on paused parser ***\n\n"); - abort(); -} - -int -dontcall_header_field_cb (http_parser *p, const char *buf, size_t len) -{ - if (p || buf || len) { } // gcc - fprintf(stderr, "\n\n*** on_header_field() called on paused parser ***\n\n"); - abort(); -} - -int -dontcall_header_value_cb (http_parser *p, const char *buf, size_t len) -{ - if (p || buf || len) { } // gcc - fprintf(stderr, "\n\n*** on_header_value() called on paused parser ***\n\n"); - abort(); -} - -int -dontcall_request_url_cb (http_parser *p, const char *buf, size_t len) -{ - if (p || buf || len) { } // gcc - fprintf(stderr, "\n\n*** on_request_url() called on paused parser ***\n\n"); - abort(); -} - -int -dontcall_body_cb (http_parser *p, const char *buf, size_t len) -{ - if (p || buf || len) { } // gcc - fprintf(stderr, "\n\n*** on_body_cb() called on paused parser ***\n\n"); - abort(); -} - -int -dontcall_headers_complete_cb (http_parser *p) -{ - if (p) { } // gcc - fprintf(stderr, "\n\n*** on_headers_complete() called on paused " - "parser ***\n\n"); - abort(); -} - -int -dontcall_message_complete_cb (http_parser *p) -{ - if (p) { } // gcc - fprintf(stderr, "\n\n*** on_message_complete() called on paused " - "parser ***\n\n"); - abort(); -} - -int -dontcall_response_status_cb (http_parser *p, const char *buf, size_t len) -{ - if (p || buf || len) { } // gcc - fprintf(stderr, "\n\n*** on_status() called on paused parser ***\n\n"); - abort(); -} - -int -dontcall_chunk_header_cb (http_parser *p) -{ - if (p) { } // gcc - fprintf(stderr, "\n\n*** on_chunk_header() called on paused parser ***\n\n"); - exit(1); -} - -int -dontcall_chunk_complete_cb (http_parser *p) -{ - if (p) { } // gcc - fprintf(stderr, "\n\n*** on_chunk_complete() " - "called on paused parser ***\n\n"); - exit(1); -} - -static http_parser_settings settings_dontcall = - {.on_message_begin = dontcall_message_begin_cb - ,.on_header_field = dontcall_header_field_cb - ,.on_header_value = dontcall_header_value_cb - ,.on_url = dontcall_request_url_cb - ,.on_status = dontcall_response_status_cb - ,.on_body = dontcall_body_cb - ,.on_headers_complete = dontcall_headers_complete_cb - ,.on_message_complete = dontcall_message_complete_cb - ,.on_chunk_header = dontcall_chunk_header_cb - ,.on_chunk_complete = dontcall_chunk_complete_cb - }; - -/* These pause_* callbacks always pause the parser and just invoke the regular - * callback that tracks content. Before returning, we overwrite the parser - * settings to point to the _dontcall variety so that we can verify that - * the pause actually did, you know, pause. */ -int -pause_message_begin_cb (http_parser *p) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return message_begin_cb(p); -} - -int -pause_header_field_cb (http_parser *p, const char *buf, size_t len) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return header_field_cb(p, buf, len); -} - -int -pause_header_value_cb (http_parser *p, const char *buf, size_t len) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return header_value_cb(p, buf, len); -} - -int -pause_request_url_cb (http_parser *p, const char *buf, size_t len) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return request_url_cb(p, buf, len); -} - -int -pause_body_cb (http_parser *p, const char *buf, size_t len) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return body_cb(p, buf, len); -} - -int -pause_headers_complete_cb (http_parser *p) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return headers_complete_cb(p); -} - -int -pause_message_complete_cb (http_parser *p) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return message_complete_cb(p); -} - -int -pause_response_status_cb (http_parser *p, const char *buf, size_t len) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return response_status_cb(p, buf, len); -} - -int -pause_chunk_header_cb (http_parser *p) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return chunk_header_cb(p); -} - -int -pause_chunk_complete_cb (http_parser *p) -{ - http_parser_pause(p, 1); - *current_pause_parser = settings_dontcall; - return chunk_complete_cb(p); -} - -static http_parser_settings settings_pause = - {.on_message_begin = pause_message_begin_cb - ,.on_header_field = pause_header_field_cb - ,.on_header_value = pause_header_value_cb - ,.on_url = pause_request_url_cb - ,.on_status = pause_response_status_cb - ,.on_body = pause_body_cb - ,.on_headers_complete = pause_headers_complete_cb - ,.on_message_complete = pause_message_complete_cb - ,.on_chunk_header = pause_chunk_header_cb - ,.on_chunk_complete = pause_chunk_complete_cb - }; - -static http_parser_settings settings = - {.on_message_begin = message_begin_cb - ,.on_header_field = header_field_cb - ,.on_header_value = header_value_cb - ,.on_url = request_url_cb - ,.on_status = response_status_cb - ,.on_body = body_cb - ,.on_headers_complete = headers_complete_cb - ,.on_message_complete = message_complete_cb - ,.on_chunk_header = chunk_header_cb - ,.on_chunk_complete = chunk_complete_cb - }; - -static http_parser_settings settings_count_body = - {.on_message_begin = message_begin_cb - ,.on_header_field = header_field_cb - ,.on_header_value = header_value_cb - ,.on_url = request_url_cb - ,.on_status = response_status_cb - ,.on_body = count_body_cb - ,.on_headers_complete = headers_complete_cb - ,.on_message_complete = message_complete_cb - ,.on_chunk_header = chunk_header_cb - ,.on_chunk_complete = chunk_complete_cb - }; - -static http_parser_settings settings_null = - {.on_message_begin = 0 - ,.on_header_field = 0 - ,.on_header_value = 0 - ,.on_url = 0 - ,.on_status = 0 - ,.on_body = 0 - ,.on_headers_complete = 0 - ,.on_message_complete = 0 - ,.on_chunk_header = 0 - ,.on_chunk_complete = 0 - }; - -void -parser_init (enum http_parser_type type) -{ - num_messages = 0; - - assert(parser == NULL); - - parser = malloc(sizeof(http_parser)); - - http_parser_init(parser, type); - - memset(&messages, 0, sizeof messages); - -} - -void -parser_free () -{ - assert(parser); - free(parser); - parser = NULL; -} - -size_t parse (const char *buf, size_t len) -{ - size_t nparsed; - currently_parsing_eof = (len == 0); - nparsed = http_parser_execute(parser, &settings, buf, len); - return nparsed; -} - -size_t parse_count_body (const char *buf, size_t len) -{ - size_t nparsed; - currently_parsing_eof = (len == 0); - nparsed = http_parser_execute(parser, &settings_count_body, buf, len); - return nparsed; -} - -size_t parse_pause (const char *buf, size_t len) -{ - size_t nparsed; - http_parser_settings s = settings_pause; - - currently_parsing_eof = (len == 0); - current_pause_parser = &s; - nparsed = http_parser_execute(parser, current_pause_parser, buf, len); - return nparsed; -} - -static inline int -check_str_eq (const struct message *m, - const char *prop, - const char *expected, - const char *found) { - if ((expected == NULL) != (found == NULL)) { - printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); - printf("expected %s\n", (expected == NULL) ? "NULL" : expected); - printf(" found %s\n", (found == NULL) ? "NULL" : found); - return 0; - } - if (expected != NULL && 0 != strcmp(expected, found)) { - printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); - printf("expected '%s'\n", expected); - printf(" found '%s'\n", found); - return 0; - } - return 1; -} - -static inline int -check_num_eq (const struct message *m, - const char *prop, - int expected, - int found) { - if (expected != found) { - printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); - printf("expected %d\n", expected); - printf(" found %d\n", found); - return 0; - } - return 1; -} - -#define MESSAGE_CHECK_STR_EQ(expected, found, prop) \ - if (!check_str_eq(expected, #prop, expected->prop, found->prop)) return 0 - -#define MESSAGE_CHECK_NUM_EQ(expected, found, prop) \ - if (!check_num_eq(expected, #prop, expected->prop, found->prop)) return 0 - -#define MESSAGE_CHECK_URL_EQ(u, expected, found, prop, fn) \ -do { \ - char ubuf[256]; \ - \ - if ((u)->field_set & (1 << (fn))) { \ - memcpy(ubuf, (found)->request_url + (u)->field_data[(fn)].off, \ - (u)->field_data[(fn)].len); \ - ubuf[(u)->field_data[(fn)].len] = '\0'; \ - } else { \ - ubuf[0] = '\0'; \ - } \ - \ - check_str_eq(expected, #prop, expected->prop, ubuf); \ -} while(0) - -int -message_eq (int index, const struct message *expected) -{ - int i; - struct message *m = &messages[index]; - - MESSAGE_CHECK_NUM_EQ(expected, m, http_major); - MESSAGE_CHECK_NUM_EQ(expected, m, http_minor); - - if (expected->type == HTTP_REQUEST) { - MESSAGE_CHECK_NUM_EQ(expected, m, method); - } else { - MESSAGE_CHECK_NUM_EQ(expected, m, status_code); - MESSAGE_CHECK_STR_EQ(expected, m, response_status); - } - - MESSAGE_CHECK_NUM_EQ(expected, m, should_keep_alive); - MESSAGE_CHECK_NUM_EQ(expected, m, message_complete_on_eof); - - assert(m->message_begin_cb_called); - assert(m->headers_complete_cb_called); - assert(m->message_complete_cb_called); - - - MESSAGE_CHECK_STR_EQ(expected, m, request_url); - - /* Check URL components; we can't do this w/ CONNECT since it doesn't - * send us a well-formed URL. - */ - if (*m->request_url && m->method != HTTP_CONNECT) { - struct http_parser_url u; - - if (http_parser_parse_url(m->request_url, strlen(m->request_url), 0, &u)) { - fprintf(stderr, "\n\n*** failed to parse URL %s ***\n\n", - m->request_url); - abort(); - } - - if (expected->host) { - MESSAGE_CHECK_URL_EQ(&u, expected, m, host, UF_HOST); - } - - if (expected->userinfo) { - MESSAGE_CHECK_URL_EQ(&u, expected, m, userinfo, UF_USERINFO); - } - - m->port = (u.field_set & (1 << UF_PORT)) ? - u.port : 0; - - MESSAGE_CHECK_URL_EQ(&u, expected, m, query_string, UF_QUERY); - MESSAGE_CHECK_URL_EQ(&u, expected, m, fragment, UF_FRAGMENT); - MESSAGE_CHECK_URL_EQ(&u, expected, m, request_path, UF_PATH); - MESSAGE_CHECK_NUM_EQ(expected, m, port); - } - - if (expected->body_size) { - MESSAGE_CHECK_NUM_EQ(expected, m, body_size); - } else { - MESSAGE_CHECK_STR_EQ(expected, m, body); - } - - assert(m->num_chunks == m->num_chunks_complete); - MESSAGE_CHECK_NUM_EQ(expected, m, num_chunks_complete); - for (i = 0; i < m->num_chunks && i < MAX_CHUNKS; i++) { - MESSAGE_CHECK_NUM_EQ(expected, m, chunk_lengths[i]); - } - - MESSAGE_CHECK_NUM_EQ(expected, m, num_headers); - - int r; - for (i = 0; i < m->num_headers; i++) { - r = check_str_eq(expected, "header field", expected->headers[i][0], m->headers[i][0]); - if (!r) return 0; - r = check_str_eq(expected, "header value", expected->headers[i][1], m->headers[i][1]); - if (!r) return 0; - } - - MESSAGE_CHECK_STR_EQ(expected, m, upgrade); - - return 1; -} - -/* Given a sequence of varargs messages, return the number of them that the - * parser should successfully parse, taking into account that upgraded - * messages prevent all subsequent messages from being parsed. - */ -size_t -count_parsed_messages(const size_t nmsgs, ...) { - size_t i; - va_list ap; - - va_start(ap, nmsgs); - - for (i = 0; i < nmsgs; i++) { - struct message *m = va_arg(ap, struct message *); - - if (m->upgrade) { - va_end(ap); - return i + 1; - } - } - - va_end(ap); - return nmsgs; -} - -/* Given a sequence of bytes and the number of these that we were able to - * parse, verify that upgrade bodies are correct. - */ -void -upgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) { - va_list ap; - size_t i; - size_t off = 0; - - va_start(ap, nmsgs); - - for (i = 0; i < nmsgs; i++) { - struct message *m = va_arg(ap, struct message *); - - off += strlen(m->raw); - - if (m->upgrade) { - off -= strlen(m->upgrade); - - /* Check the portion of the response after its specified upgrade */ - if (!check_str_eq(m, "upgrade", body + off, body + nread)) { - abort(); - } - - /* Fix up the response so that message_eq() will verify the beginning - * of the upgrade */ - *(body + nread + strlen(m->upgrade)) = '\0'; - messages[num_messages -1 ].upgrade = body + nread; - - va_end(ap); - return; - } - } - - va_end(ap); - printf("\n\n*** Error: expected a message with upgrade ***\n"); - - abort(); -} - -static void -print_error (const char *raw, size_t error_location) -{ - fprintf(stderr, "\n*** %s ***\n\n", - http_errno_description(HTTP_PARSER_ERRNO(parser))); - - int this_line = 0, char_len = 0; - size_t i, j, len = strlen(raw), error_location_line = 0; - for (i = 0; i < len; i++) { - if (i == error_location) this_line = 1; - switch (raw[i]) { - case '\r': - char_len = 2; - fprintf(stderr, "\\r"); - break; - - case '\n': - fprintf(stderr, "\\n\n"); - - if (this_line) goto print; - - error_location_line = 0; - continue; - - default: - char_len = 1; - fputc(raw[i], stderr); - break; - } - if (!this_line) error_location_line += char_len; - } - - fprintf(stderr, "[eof]\n"); - - print: - for (j = 0; j < error_location_line; j++) { - fputc(' ', stderr); - } - fprintf(stderr, "^\n\nerror location: %u\n", (unsigned int)error_location); -} - -void -test_preserve_data (void) -{ - char my_data[] = "application-specific data"; - http_parser parser; - parser.data = my_data; - http_parser_init(&parser, HTTP_REQUEST); - if (parser.data != my_data) { - printf("\n*** parser.data not preserved accross http_parser_init ***\n\n"); - abort(); - } -} - -struct url_test { - const char *name; - const char *url; - int is_connect; - struct http_parser_url u; - int rv; -}; - -const struct url_test url_tests[] = -{ {.name="proxy request" - ,.url="http://hostname/" - ,.is_connect=0 - ,.u= - {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) - ,.port=0 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 7, 8 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 15, 1 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="proxy request with port" - ,.url="http://hostname:444/" - ,.is_connect=0 - ,.u= - {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH) - ,.port=444 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 7, 8 } /* UF_HOST */ - ,{ 16, 3 } /* UF_PORT */ - ,{ 19, 1 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="CONNECT request" - ,.url="hostname:443" - ,.is_connect=1 - ,.u= - {.field_set=(1 << UF_HOST) | (1 << UF_PORT) - ,.port=443 - ,.field_data= - {{ 0, 0 } /* UF_SCHEMA */ - ,{ 0, 8 } /* UF_HOST */ - ,{ 9, 3 } /* UF_PORT */ - ,{ 0, 0 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="CONNECT request but not connect" - ,.url="hostname:443" - ,.is_connect=0 - ,.rv=1 - } - -, {.name="proxy ipv6 request" - ,.url="http://[1:2::3:4]/" - ,.is_connect=0 - ,.u= - {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) - ,.port=0 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 8, 8 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 17, 1 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="proxy ipv6 request with port" - ,.url="http://[1:2::3:4]:67/" - ,.is_connect=0 - ,.u= - {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH) - ,.port=67 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 8, 8 } /* UF_HOST */ - ,{ 18, 2 } /* UF_PORT */ - ,{ 20, 1 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="CONNECT ipv6 address" - ,.url="[1:2::3:4]:443" - ,.is_connect=1 - ,.u= - {.field_set=(1 << UF_HOST) | (1 << UF_PORT) - ,.port=443 - ,.field_data= - {{ 0, 0 } /* UF_SCHEMA */ - ,{ 1, 8 } /* UF_HOST */ - ,{ 11, 3 } /* UF_PORT */ - ,{ 0, 0 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="ipv4 in ipv6 address" - ,.url="http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/" - ,.is_connect=0 - ,.u= - {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) - ,.port=0 - ,.field_data= - {{ 0, 4 } /* UF_SCHEMA */ - ,{ 8, 37 } /* UF_HOST */ - ,{ 0, 0 } /* UF_PORT */ - ,{ 46, 1 } /* UF_PATH */ - ,{ 0, 0 } /* UF_QUERY */ - ,{ 0, 0 } /* UF_FRAGMENT */ - ,{ 0, 0 } /* UF_USERINFO */ - } - } - ,.rv=0 - } - -, {.name="extra ? in query string" - ,.url="http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css," - "fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css," - "fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css" - ,.is_connect=0 - ,.u= - {.field_set=(1<field_set, u->port); - for (i = 0; i < UF_MAX; i++) { - if ((u->field_set & (1 << i)) == 0) { - printf("\tfield_data[%u]: unset\n", i); - continue; - } - - printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n\"", - i, - u->field_data[i].off, - u->field_data[i].len, - u->field_data[i].len, - url + u->field_data[i].off); - } -} - -void -test_parse_url (void) -{ - struct http_parser_url u; - const struct url_test *test; - unsigned int i; - int rv; - - for (i = 0; i < (sizeof(url_tests) / sizeof(url_tests[0])); i++) { - test = &url_tests[i]; - memset(&u, 0, sizeof(u)); - - rv = http_parser_parse_url(test->url, - strlen(test->url), - test->is_connect, - &u); - - if (test->rv == 0) { - if (rv != 0) { - printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " - "unexpected rv %d ***\n\n", test->url, test->name, rv); - abort(); - } - - if (memcmp(&u, &test->u, sizeof(u)) != 0) { - printf("\n*** http_parser_parse_url(\"%s\") \"%s\" failed ***\n", - test->url, test->name); - - printf("target http_parser_url:\n"); - dump_url(test->url, &test->u); - printf("result http_parser_url:\n"); - dump_url(test->url, &u); - - abort(); - } - } else { - /* test->rv != 0 */ - if (rv == 0) { - printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " - "unexpected rv %d ***\n\n", test->url, test->name, rv); - abort(); - } - } - } -} - -void -test_method_str (void) -{ - assert(0 == strcmp("GET", http_method_str(HTTP_GET))); - assert(0 == strcmp("", http_method_str(1337))); -} - -void -test_message (const struct message *message) -{ - size_t raw_len = strlen(message->raw); - size_t msg1len; - for (msg1len = 0; msg1len < raw_len; msg1len++) { - parser_init(message->type); - - size_t read; - const char *msg1 = message->raw; - const char *msg2 = msg1 + msg1len; - size_t msg2len = raw_len - msg1len; - - if (msg1len) { - read = parse(msg1, msg1len); - - if (message->upgrade && parser->upgrade && num_messages > 0) { - messages[num_messages - 1].upgrade = msg1 + read; - goto test; - } - - if (read != msg1len) { - print_error(msg1, read); - abort(); - } - } - - - read = parse(msg2, msg2len); - - if (message->upgrade && parser->upgrade) { - messages[num_messages - 1].upgrade = msg2 + read; - goto test; - } - - if (read != msg2len) { - print_error(msg2, read); - abort(); - } - - read = parse(NULL, 0); - - if (read != 0) { - print_error(message->raw, read); - abort(); - } - - test: - - if (num_messages != 1) { - printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); - abort(); - } - - if(!message_eq(0, message)) abort(); - - parser_free(); - } -} - -void -test_message_count_body (const struct message *message) -{ - parser_init(message->type); - - size_t read; - size_t l = strlen(message->raw); - size_t i, toread; - size_t chunk = 4024; - - for (i = 0; i < l; i+= chunk) { - toread = MIN(l-i, chunk); - read = parse_count_body(message->raw + i, toread); - if (read != toread) { - print_error(message->raw, read); - abort(); - } - } - - - read = parse_count_body(NULL, 0); - if (read != 0) { - print_error(message->raw, read); - abort(); - } - - if (num_messages != 1) { - printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); - abort(); - } - - if(!message_eq(0, message)) abort(); - - parser_free(); -} - -void -test_simple (const char *buf, enum http_errno err_expected) -{ - parser_init(HTTP_REQUEST); - - enum http_errno err; - - parse(buf, strlen(buf)); - err = HTTP_PARSER_ERRNO(parser); - parse(NULL, 0); - - parser_free(); - - /* In strict mode, allow us to pass with an unexpected HPE_STRICT as - * long as the caller isn't expecting success. - */ -#if HTTP_PARSER_STRICT - if (err_expected != err && err_expected != HPE_OK && err != HPE_STRICT) { -#else - if (err_expected != err) { -#endif - fprintf(stderr, "\n*** test_simple expected %s, but saw %s ***\n\n%s\n", - http_errno_name(err_expected), http_errno_name(err), buf); - abort(); - } -} - -void -test_header_overflow_error (int req) -{ - http_parser parser; - http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); - size_t parsed; - const char *buf; - buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.0 200 OK\r\n"; - parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); - assert(parsed == strlen(buf)); - - buf = "header-key: header-value\r\n"; - size_t buflen = strlen(buf); - - int i; - for (i = 0; i < 10000; i++) { - parsed = http_parser_execute(&parser, &settings_null, buf, buflen); - if (parsed != buflen) { - //fprintf(stderr, "error found on iter %d\n", i); - assert(HTTP_PARSER_ERRNO(&parser) == HPE_HEADER_OVERFLOW); - return; - } - } - - fprintf(stderr, "\n*** Error expected but none in header overflow test ***\n"); - abort(); -} - - -void -test_header_nread_value () -{ - http_parser parser; - http_parser_init(&parser, HTTP_REQUEST); - size_t parsed; - const char *buf; - buf = "GET / HTTP/1.1\r\nheader: value\nhdr: value\r\n"; - parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); - assert(parsed == strlen(buf)); - - assert(parser.nread == strlen(buf)); -} - - -static void -test_content_length_overflow (const char *buf, size_t buflen, int expect_ok) -{ - http_parser parser; - http_parser_init(&parser, HTTP_RESPONSE); - http_parser_execute(&parser, &settings_null, buf, buflen); - - if (expect_ok) - assert(HTTP_PARSER_ERRNO(&parser) == HPE_OK); - else - assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_CONTENT_LENGTH); -} - -void -test_header_content_length_overflow_error (void) -{ -#define X(size) \ - "HTTP/1.1 200 OK\r\n" \ - "Content-Length: " #size "\r\n" \ - "\r\n" - const char a[] = X(1844674407370955160); /* 2^64 / 10 - 1 */ - const char b[] = X(18446744073709551615); /* 2^64-1 */ - const char c[] = X(18446744073709551616); /* 2^64 */ -#undef X - test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */ - test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */ - test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */ -} - -void -test_chunk_content_length_overflow_error (void) -{ -#define X(size) \ - "HTTP/1.1 200 OK\r\n" \ - "Transfer-Encoding: chunked\r\n" \ - "\r\n" \ - #size "\r\n" \ - "..." - const char a[] = X(FFFFFFFFFFFFFFE); /* 2^64 / 16 - 1 */ - const char b[] = X(FFFFFFFFFFFFFFFF); /* 2^64-1 */ - const char c[] = X(10000000000000000); /* 2^64 */ -#undef X - test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */ - test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */ - test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */ -} - -void -test_no_overflow_long_body (int req, size_t length) -{ - http_parser parser; - http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); - size_t parsed; - size_t i; - char buf1[3000]; - size_t buf1len = sprintf(buf1, "%s\r\nConnection: Keep-Alive\r\nContent-Length: %lu\r\n\r\n", - req ? "POST / HTTP/1.0" : "HTTP/1.0 200 OK", (unsigned long)length); - parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len); - if (parsed != buf1len) - goto err; - - for (i = 0; i < length; i++) { - char foo = 'a'; - parsed = http_parser_execute(&parser, &settings_null, &foo, 1); - if (parsed != 1) - goto err; - } - - parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len); - if (parsed != buf1len) goto err; - return; - - err: - fprintf(stderr, - "\n*** error in test_no_overflow_long_body %s of length %lu ***\n", - req ? "REQUEST" : "RESPONSE", - (unsigned long)length); - abort(); -} - -void -test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3) -{ - int message_count = count_parsed_messages(3, r1, r2, r3); - - char total[ strlen(r1->raw) - + strlen(r2->raw) - + strlen(r3->raw) - + 1 - ]; - total[0] = '\0'; - - strcat(total, r1->raw); - strcat(total, r2->raw); - strcat(total, r3->raw); - - parser_init(r1->type); - - size_t read; - - read = parse(total, strlen(total)); - - if (parser->upgrade) { - upgrade_message_fix(total, read, 3, r1, r2, r3); - goto test; - } - - if (read != strlen(total)) { - print_error(total, read); - abort(); - } - - read = parse(NULL, 0); - - if (read != 0) { - print_error(total, read); - abort(); - } - -test: - - if (message_count != num_messages) { - fprintf(stderr, "\n\n*** Parser didn't see 3 messages only %d *** \n", num_messages); - abort(); - } - - if (!message_eq(0, r1)) abort(); - if (message_count > 1 && !message_eq(1, r2)) abort(); - if (message_count > 2 && !message_eq(2, r3)) abort(); - - parser_free(); -} - -/* SCAN through every possible breaking to make sure the - * parser can handle getting the content in any chunks that - * might come from the socket - */ -void -test_scan (const struct message *r1, const struct message *r2, const struct message *r3) -{ - char total[80*1024] = "\0"; - char buf1[80*1024] = "\0"; - char buf2[80*1024] = "\0"; - char buf3[80*1024] = "\0"; - - strcat(total, r1->raw); - strcat(total, r2->raw); - strcat(total, r3->raw); - - size_t read; - - int total_len = strlen(total); - - int total_ops = 2 * (total_len - 1) * (total_len - 2) / 2; - int ops = 0 ; - - size_t buf1_len, buf2_len, buf3_len; - int message_count = count_parsed_messages(3, r1, r2, r3); - - int i,j,type_both; - for (type_both = 0; type_both < 2; type_both ++ ) { - for (j = 2; j < total_len; j ++ ) { - for (i = 1; i < j; i ++ ) { - - if (ops % 1000 == 0) { - printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops); - fflush(stdout); - } - ops += 1; - - parser_init(type_both ? HTTP_BOTH : r1->type); - - buf1_len = i; - strlncpy(buf1, sizeof(buf1), total, buf1_len); - buf1[buf1_len] = 0; - - buf2_len = j - i; - strlncpy(buf2, sizeof(buf1), total+i, buf2_len); - buf2[buf2_len] = 0; - - buf3_len = total_len - j; - strlncpy(buf3, sizeof(buf1), total+j, buf3_len); - buf3[buf3_len] = 0; - - read = parse(buf1, buf1_len); - - if (parser->upgrade) goto test; - - if (read != buf1_len) { - print_error(buf1, read); - goto error; - } - - read += parse(buf2, buf2_len); - - if (parser->upgrade) goto test; - - if (read != buf1_len + buf2_len) { - print_error(buf2, read); - goto error; - } - - read += parse(buf3, buf3_len); - - if (parser->upgrade) goto test; - - if (read != buf1_len + buf2_len + buf3_len) { - print_error(buf3, read); - goto error; - } - - parse(NULL, 0); - -test: - if (parser->upgrade) { - upgrade_message_fix(total, read, 3, r1, r2, r3); - } - - if (message_count != num_messages) { - fprintf(stderr, "\n\nParser didn't see %d messages only %d\n", - message_count, num_messages); - goto error; - } - - if (!message_eq(0, r1)) { - fprintf(stderr, "\n\nError matching messages[0] in test_scan.\n"); - goto error; - } - - if (message_count > 1 && !message_eq(1, r2)) { - fprintf(stderr, "\n\nError matching messages[1] in test_scan.\n"); - goto error; - } - - if (message_count > 2 && !message_eq(2, r3)) { - fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n"); - goto error; - } - - parser_free(); - } - } - } - puts("\b\b\b\b100%"); - return; - - error: - fprintf(stderr, "i=%d j=%d\n", i, j); - fprintf(stderr, "buf1 (%u) %s\n\n", (unsigned int)buf1_len, buf1); - fprintf(stderr, "buf2 (%u) %s\n\n", (unsigned int)buf2_len , buf2); - fprintf(stderr, "buf3 (%u) %s\n", (unsigned int)buf3_len, buf3); - abort(); -} - -// user required to free the result -// string terminated by \0 -char * -create_large_chunked_message (int body_size_in_kb, const char* headers) -{ - int i; - size_t wrote = 0; - size_t headers_len = strlen(headers); - size_t bufsize = headers_len + (5+1024+2)*body_size_in_kb + 6; - char * buf = malloc(bufsize); - - memcpy(buf, headers, headers_len); - wrote += headers_len; - - for (i = 0; i < body_size_in_kb; i++) { - // write 1kb chunk into the body. - memcpy(buf + wrote, "400\r\n", 5); - wrote += 5; - memset(buf + wrote, 'C', 1024); - wrote += 1024; - strcpy(buf + wrote, "\r\n"); - wrote += 2; - } - - memcpy(buf + wrote, "0\r\n\r\n", 6); - wrote += 6; - assert(wrote == bufsize); - - return buf; -} - -/* Verify that we can pause parsing at any of the bytes in the - * message and still get the result that we're expecting. */ -void -test_message_pause (const struct message *msg) -{ - char *buf = (char*) msg->raw; - size_t buflen = strlen(msg->raw); - size_t nread; - - parser_init(msg->type); - - do { - nread = parse_pause(buf, buflen); - - // We can only set the upgrade buffer once we've gotten our message - // completion callback. - if (messages[0].message_complete_cb_called && - msg->upgrade && - parser->upgrade) { - messages[0].upgrade = buf + nread; - goto test; - } - - if (nread < buflen) { - - // Not much do to if we failed a strict-mode check - if (HTTP_PARSER_ERRNO(parser) == HPE_STRICT) { - parser_free(); - return; - } - - assert (HTTP_PARSER_ERRNO(parser) == HPE_PAUSED); - } - - buf += nread; - buflen -= nread; - http_parser_pause(parser, 0); - } while (buflen > 0); - - nread = parse_pause(NULL, 0); - assert (nread == 0); - -test: - if (num_messages != 1) { - printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name); - abort(); - } - - if(!message_eq(0, msg)) abort(); - - parser_free(); -} - -int -main (void) -{ - parser = NULL; - int i, j, k; - int request_count; - int response_count; - unsigned long version; - unsigned major; - unsigned minor; - unsigned patch; - - version = http_parser_version(); - major = (version >> 16) & 255; - minor = (version >> 8) & 255; - patch = version & 255; - printf("http_parser v%u.%u.%u (0x%06lx)\n", major, minor, patch, version); - - printf("sizeof(http_parser) = %u\n", (unsigned int)sizeof(http_parser)); - - for (request_count = 0; requests[request_count].name; request_count++); - for (response_count = 0; responses[response_count].name; response_count++); - - //// API - test_preserve_data(); - test_parse_url(); - test_method_str(); - - //// NREAD - test_header_nread_value(); - - //// OVERFLOW CONDITIONS - - test_header_overflow_error(HTTP_REQUEST); - test_no_overflow_long_body(HTTP_REQUEST, 1000); - test_no_overflow_long_body(HTTP_REQUEST, 100000); - - test_header_overflow_error(HTTP_RESPONSE); - test_no_overflow_long_body(HTTP_RESPONSE, 1000); - test_no_overflow_long_body(HTTP_RESPONSE, 100000); - - test_header_content_length_overflow_error(); - test_chunk_content_length_overflow_error(); - - //// RESPONSES - - for (i = 0; i < response_count; i++) { - test_message(&responses[i]); - } - - for (i = 0; i < response_count; i++) { - test_message_pause(&responses[i]); - } - - for (i = 0; i < response_count; i++) { - if (!responses[i].should_keep_alive) continue; - for (j = 0; j < response_count; j++) { - if (!responses[j].should_keep_alive) continue; - for (k = 0; k < response_count; k++) { - test_multiple3(&responses[i], &responses[j], &responses[k]); - } - } - } - - test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]); - test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]); - - // test very large chunked response - { - char * msg = create_large_chunked_message(31337, - "HTTP/1.0 200 OK\r\n" - "Transfer-Encoding: chunked\r\n" - "Content-Type: text/plain\r\n" - "\r\n"); - struct message large_chunked = - {.name= "large chunked" - ,.type= HTTP_RESPONSE - ,.raw= msg - ,.should_keep_alive= FALSE - ,.message_complete_on_eof= FALSE - ,.http_major= 1 - ,.http_minor= 0 - ,.status_code= 200 - ,.response_status= "OK" - ,.num_headers= 2 - ,.headers= - { { "Transfer-Encoding", "chunked" } - , { "Content-Type", "text/plain" } - } - ,.body_size= 31337*1024 - ,.num_chunks_complete= 31338 - }; - for (i = 0; i < MAX_CHUNKS; i++) { - large_chunked.chunk_lengths[i] = 1024; - } - test_message_count_body(&large_chunked); - free(msg); - } - - - - printf("response scan 1/2 "); - test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY] - , &responses[NO_BODY_HTTP10_KA_204] - , &responses[NO_REASON_PHRASE] - ); - - printf("response scan 2/2 "); - test_scan( &responses[BONJOUR_MADAME_FR] - , &responses[UNDERSTORE_HEADER_KEY] - , &responses[NO_CARRIAGE_RET] - ); - - puts("responses okay"); - - - /// REQUESTS - - test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION); - - // Well-formed but incomplete - test_simple("GET / HTTP/1.1\r\n" - "Content-Type: text/plain\r\n" - "Content-Length: 6\r\n" - "\r\n" - "fooba", - HPE_OK); - - static const char *all_methods[] = { - "DELETE", - "GET", - "HEAD", - "POST", - "PUT", - //"CONNECT", //CONNECT can't be tested like other methods, it's a tunnel - "OPTIONS", - "TRACE", - "COPY", - "LOCK", - "MKCOL", - "MOVE", - "PROPFIND", - "PROPPATCH", - "UNLOCK", - "REPORT", - "MKACTIVITY", - "CHECKOUT", - "MERGE", - "M-SEARCH", - "NOTIFY", - "SUBSCRIBE", - "UNSUBSCRIBE", - "PATCH", - 0 }; - const char **this_method; - for (this_method = all_methods; *this_method; this_method++) { - char buf[200]; - sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); - test_simple(buf, HPE_OK); - } - - static const char *bad_methods[] = { - "ASDF", - "C******", - "COLA", - "GEM", - "GETA", - "M****", - "MKCOLA", - "PROPPATCHA", - "PUN", - "PX", - "SA", - "hello world", - 0 }; - for (this_method = bad_methods; *this_method; this_method++) { - char buf[200]; - sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); - test_simple(buf, HPE_INVALID_METHOD); - } - - // illegal header field name line folding - test_simple("GET / HTTP/1.1\r\n" - "name\r\n" - " : value\r\n" - "\r\n", - HPE_INVALID_HEADER_TOKEN); - - const char *dumbfuck2 = - "GET / HTTP/1.1\r\n" - "X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n" - "\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n" - "\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n" - "\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n" - "\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n" - "\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n" - "\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n" - "\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n" - "\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n" - "\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n" - "\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n" - "\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n" - "\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n" - "\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n" - "\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n" - "\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n" - "\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n" - "\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n" - "\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n" - "\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n" - "\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n" - "\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n" - "\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n" - "\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n" - "\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n" - "\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n" - "\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n" - "\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n" - "\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n" - "\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n" - "\tRA==\r\n" - "\t-----END CERTIFICATE-----\r\n" - "\r\n"; - test_simple(dumbfuck2, HPE_OK); - - const char *corrupted_connection = - "GET / HTTP/1.1\r\n" - "Host: www.example.com\r\n" - "Connection\r\033\065\325eep-Alive\r\n" - "Accept-Encoding: gzip\r\n" - "\r\n"; - test_simple(corrupted_connection, HPE_INVALID_HEADER_TOKEN); - - const char *corrupted_header_name = - "GET / HTTP/1.1\r\n" - "Host: www.example.com\r\n" - "X-Some-Header\r\033\065\325eep-Alive\r\n" - "Accept-Encoding: gzip\r\n" - "\r\n"; - test_simple(corrupted_header_name, HPE_INVALID_HEADER_TOKEN); - -#if 0 - // NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body - // until EOF. - // - // no content-length - // error if there is a body without content length - const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n" - "Accept: */*\r\n" - "\r\n" - "HELLO"; - test_simple(bad_get_no_headers_no_body, 0); -#endif - /* TODO sending junk and large headers gets rejected */ - - - /* check to make sure our predefined requests are okay */ - for (i = 0; requests[i].name; i++) { - test_message(&requests[i]); - } - - for (i = 0; i < request_count; i++) { - test_message_pause(&requests[i]); - } - - for (i = 0; i < request_count; i++) { - if (!requests[i].should_keep_alive) continue; - for (j = 0; j < request_count; j++) { - if (!requests[j].should_keep_alive) continue; - for (k = 0; k < request_count; k++) { - test_multiple3(&requests[i], &requests[j], &requests[k]); - } - } - } - - printf("request scan 1/4 "); - test_scan( &requests[GET_NO_HEADERS_NO_BODY] - , &requests[GET_ONE_HEADER_NO_BODY] - , &requests[GET_NO_HEADERS_NO_BODY] - ); - - printf("request scan 2/4 "); - test_scan( &requests[POST_CHUNKED_ALL_YOUR_BASE] - , &requests[POST_IDENTITY_BODY_WORLD] - , &requests[GET_FUNKY_CONTENT_LENGTH] - ); - - printf("request scan 3/4 "); - test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END] - , &requests[CHUNKED_W_TRAILING_HEADERS] - , &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH] - ); - - printf("request scan 4/4 "); - test_scan( &requests[QUERY_URL_WITH_QUESTION_MARK_GET] - , &requests[PREFIX_NEWLINE_GET ] - , &requests[CONNECT_REQUEST] - ); - - puts("requests okay"); - - return 0; -} diff --git a/doc/api/process.markdown b/doc/api/process.markdown index ebff3d6496a644..f2586843f03183 100644 --- a/doc/api/process.markdown +++ b/doc/api/process.markdown @@ -632,8 +632,7 @@ A property exposing version strings of io.js and its dependencies. Will print something like: - { http_parser: '2.3.0', - node: '1.1.1', + { node: '1.1.1', v8: '4.1.0.14', uv: '1.3.0', zlib: '1.2.8', @@ -660,7 +659,6 @@ An example of the possible output looks like: node_install_npm: 'true', node_prefix: '', node_shared_cares: 'false', - node_shared_http_parser: 'false', node_shared_libuv: 'false', node_shared_zlib: 'false', node_use_dtrace: 'false', diff --git a/lib/_http_client.js b/lib/_http_client.js index a7d714f7e0b0b2..b047107a5513c5 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -4,7 +4,7 @@ const util = require('util'); const net = require('net'); const url = require('url'); const EventEmitter = require('events').EventEmitter; -const HTTPParser = process.binding('http_parser').HTTPParser; +const HTTPParser = require('_http_parser'); const assert = require('assert').ok; const common = require('_http_common'); const httpSocketSetup = common.httpSocketSetup; diff --git a/lib/_http_common.js b/lib/_http_common.js index 757032929444b1..62d568bce1c8b3 100644 --- a/lib/_http_common.js +++ b/lib/_http_common.js @@ -1,7 +1,7 @@ 'use strict'; const FreeList = require('internal/freelist').FreeList; -const HTTPParser = process.binding('http_parser').HTTPParser; +const HTTPParser = require('_http_parser'); const incoming = require('_http_incoming'); const IncomingMessage = incoming.IncomingMessage; @@ -14,26 +14,6 @@ exports.debug = debug; exports.CRLF = '\r\n'; exports.chunkExpression = /chunk/i; exports.continueExpression = /100-continue/i; -exports.methods = HTTPParser.methods; - -const kOnHeaders = HTTPParser.kOnHeaders | 0; -const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0; -const kOnBody = HTTPParser.kOnBody | 0; -const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0; - -// Only called in the slow case where slow means -// that the request headers were either fragmented -// across multiple TCP packets or too large to be -// processed in a single run. This method is also -// called to process trailing HTTP headers. -function parserOnHeaders(headers, url) { - // Once we exceeded headers limit - stop collecting them - if (this.maxHeaderPairs <= 0 || - this._headers.length < this.maxHeaderPairs) { - this._headers = this._headers.concat(headers); - } - this._url += url; -} // `headers` and `url` are set only if .onHeaders() has not been called for // this request. @@ -43,22 +23,11 @@ function parserOnHeadersComplete(versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) { var parser = this; - - if (!headers) { - headers = parser._headers; - parser._headers = []; - } - - if (!url) { - url = parser._url; - parser._url = ''; - } - - parser.incoming = new IncomingMessage(parser.socket); - parser.incoming.httpVersionMajor = versionMajor; - parser.incoming.httpVersionMinor = versionMinor; - parser.incoming.httpVersion = versionMajor + '.' + versionMinor; - parser.incoming.url = url; + var stream = parser.incoming = new IncomingMessage(parser.socket); + stream.httpVersionMajor = versionMajor; + stream.httpVersionMinor = versionMinor; + stream.httpVersion = versionMajor + '.' + versionMinor; + stream.url = url; var n = headers.length; @@ -66,18 +35,18 @@ function parserOnHeadersComplete(versionMajor, versionMinor, headers, method, if (parser.maxHeaderPairs > 0) n = Math.min(n, parser.maxHeaderPairs); - parser.incoming._addHeaderLines(headers, n); + stream._addHeaderLines(headers, n); - if (typeof method === 'number') { + if (typeof method === 'string') { // server only - parser.incoming.method = HTTPParser.methods[method]; + stream.method = method; } else { // client only - parser.incoming.statusCode = statusCode; - parser.incoming.statusMessage = statusMessage; + stream.statusCode = statusCode; + stream.statusMessage = statusMessage; } - parser.incoming.upgrade = upgrade; + stream.upgrade = upgrade; var skipBody = false; // response to HEAD or CONNECT @@ -85,7 +54,7 @@ function parserOnHeadersComplete(versionMajor, versionMinor, headers, method, // For upgraded connections and CONNECT method request, we'll emit this // after parser.execute so that we can capture the first part of the new // protocol. - skipBody = parser.onIncoming(parser.incoming, shouldKeepAlive); + skipBody = parser.onIncoming(stream, shouldKeepAlive); } return skipBody; @@ -118,14 +87,11 @@ function parserOnMessageComplete() { if (stream) { stream.complete = true; - // Emit any trailing headers. - var headers = parser._headers; - if (headers) { - parser.incoming._addHeaderLines(headers, headers.length); - parser._headers = []; - parser._url = ''; - } - + // Add any trailing headers. + var headers = parser.headers; + var headerscnt = headers.length; + if (headerscnt > 0) + stream._addHeaderLines(headers, headerscnt); // For emit end event stream.push(null); } @@ -138,18 +104,9 @@ function parserOnMessageComplete() { var parsers = new FreeList('parsers', 1000, function() { var parser = new HTTPParser(HTTPParser.REQUEST); - parser._headers = []; - parser._url = ''; - - // Only called in the slow case where slow means - // that the request headers were either fragmented - // across multiple TCP packets or too large to be - // processed in a single run. This method is also - // called to process trailing HTTP headers. - parser[kOnHeaders] = parserOnHeaders; - parser[kOnHeadersComplete] = parserOnHeadersComplete; - parser[kOnBody] = parserOnBody; - parser[kOnMessageComplete] = parserOnMessageComplete; + parser.onHeaders = parserOnHeadersComplete; + parser.onBody = parserOnBody; + parser.onComplete = parserOnMessageComplete; return parser; }); @@ -165,7 +122,6 @@ exports.parsers = parsers; // should be all that is needed. function freeParser(parser, req, socket) { if (parser) { - parser._headers = []; parser.onIncoming = null; if (parser.socket) parser.socket.parser = null; diff --git a/lib/_http_parser.js b/lib/_http_parser.js new file mode 100644 index 00000000000000..972c66d8827b4a --- /dev/null +++ b/lib/_http_parser.js @@ -0,0 +1,703 @@ +var inspect = require('util').inspect; + +var CRLF = new Buffer('\r\n'); +var CR = 13; +var LF = 10; + +var MAX_CHUNK_SIZE_LEN = 16; // max length of chunk size line +var MAX_CHUNK_SIZE = 9007199254740992; +// RFC 7230 recommends at least 8000 max bytes for request line, but no +// recommendation for status lines +var MAX_HEADER_BYTES = 80 * 1024; + +var RE_PCHAR = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})/; +var RE_ABS_PATH = new RegExp('(?:/' + RE_PCHAR.source + '*)+'); +var RE_QUERY = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*/; +// Note: fragments are technically not allowed in the request line, but +// joyent/http-parser allowed it previously so we do also for backwards +// compatibility ... +var RE_ORIGIN_FORM = new RegExp('(?:' + RE_ABS_PATH.source + '(?:\\?' + RE_QUERY.source + ')?(?:#' + RE_QUERY.source + ')?)'); + +var RE_SCHEME = /[A-Za-z][A-Za-z0-9+\-.]*/; +var RE_USERINFO = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:]|%[0-9A-Fa-f]{2})*/; +var RE_IPV4_OCTET = /(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/; +var RE_IPV4 = new RegExp('(?:' + RE_IPV4_OCTET.source + '\\.){3}' + RE_IPV4_OCTET.source); +var RE_H16 = /[0-9A-Fa-f]{1,4}/; +var RE_LS32 = new RegExp('(?:' + RE_H16.source + ':' + RE_H16.source + ')|(?:' + RE_IPV4.source + ')'); +var RE_H16_COLON = new RegExp('(?:' + RE_H16.source + ':)'); +var RE_IPV6 = new RegExp('(?:' + // Begin LS32 postfix cases + + '(?:' + + [ + RE_H16_COLON.source + '{6}', + '::' + RE_H16_COLON.source + '{5}', + '(?:' + RE_H16.source + ')?::' + RE_H16_COLON.source + '{4}', + '(?:' + RE_H16_COLON.source + '{0,1}' + RE_H16.source + ')?::' + RE_H16_COLON.source + '{3}', + '(?:' + RE_H16_COLON.source + '{0,2}' + RE_H16.source + ')?::' + RE_H16_COLON.source + '{2}', + '(?:' + RE_H16_COLON.source + '{0,3}' + RE_H16.source + ')?::' + RE_H16_COLON.source, + '(?:' + RE_H16_COLON.source + '{0,4}' + RE_H16.source + ')?::', + ].join(')|(?:') + + ')(?:' + RE_LS32.source + ')' + // End LS32 postfix cases + + ')' + + '|(?:(?:' + RE_H16_COLON.source + '{0,5}' + RE_H16.source + ')?::' + RE_H16.source + ')' + + '|(?:(?:' + RE_H16_COLON.source + '{0,6}' + RE_H16.source + ')?::)'); + +var RE_REGNAME = /(?:[A-Za-z0-9\-._~!$&'()*+,;=]|%[0-9A-Fa-f]{2})*/; +var RE_HOST = new RegExp('(?:(?:\\[' + RE_IPV6.source + '\\])|(?:' + RE_IPV4.source + ')|' + RE_REGNAME.source + ')'); +var RE_AUTHORITY = new RegExp('(?:(?:' + RE_USERINFO.source + ')?' + RE_HOST.source + '(?::[0-9]*)?)'); +var RE_PATH_ABEMPTY = new RegExp('(?:/' + RE_PCHAR.source + '*)*'); +var RE_PATH_ROOTLESS = new RegExp('(?:' + RE_PCHAR.source + '+' + RE_PATH_ABEMPTY.source + ')'); +var RE_PATH_ABSOLUTE = new RegExp('(?:/' + RE_PATH_ROOTLESS.source + '?)'); +var RE_HIER_PART = new RegExp('//(?:' + RE_AUTHORITY.source + ')(?:' + RE_PATH_ABEMPTY + '|' + RE_PATH_ABSOLUTE + '|' + RE_PATH_ROOTLESS + '|)'); +// Note: fragments are technically not allowed in the request line, but +// joyent/http-parser allowed it previously so we do also for backwards +// compatibility ... +var RE_ABSOLUTE_FORM = new RegExp('(?:' + RE_SCHEME.source + ':' + RE_HIER_PART.source + '(?:\\?' + RE_QUERY.source + ')?(?:#' + RE_QUERY.source + ')?)'); + +var RE_REQUEST_TARGET = new RegExp('(?:' + RE_ORIGIN_FORM.source + '|' + RE_ABSOLUTE_FORM.source + '|' + RE_AUTHORITY.source + '|\\*)'); +var RE_REQUEST_LINE = new RegExp('^([!#$%\'*+\\-.^_`|~0-9A-Za-z]+) (' + RE_REQUEST_TARGET.source + ')(?: HTTP\\/1\\.([01]))?$'); +/* +request-target = origin-form | absolute-form | authority-form | asterisk-form + origin-form = absolute-path [ "?" query ] + absolute-path = 1*( "/" segment ) + segment = *pchar + pchar = unreserved | pct-encoded | sub-delims | ":" | "@" + unreserved = ALPHA | DIGIT | "-" | "." | "_" | "~" + pct-encoded = "%" HEXDIG HEXDIG + HEXDIG = DIGIT | "A" | "B" | "C" | "D" | "E" | "F" + sub-delims = "!" | "$" | "&" | "'" | "(" | ")" | "*" | "+" | "," | ";" | "=" + query = *( pchar | "/" | "?" ) + absolute-form = absolute-URI + absolute-URI = scheme ":" hier-part [ "?" query ] + scheme = alpha *( alpha | digit | "+" | "-" | "." ) + hier-part = "//" authority path-abempty | path-absolute | path-rootless | path-empty + authority = [ userinfo "@" ] host [ ":" port ] + userinfo = *( unreserved | pct-encoded | sub-delims | ":" ) + host = IP-literal | IPv4address | reg-name + IP-literal = "[" ( IPv6address | IPvFuture ) "]" + IPv6address = 6( h16 ":" ) ls32 + | "::" 5( h16 ":" ) ls32 + | [ h16 ] "::" 4( h16 ":" ) ls32 + | [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + | [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + | [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + | [ *4( h16 ":" ) h16 ] "::" ls32 + | [ *5( h16 ":" ) h16 ] "::" h16 + | [ *6( h16 ":" ) h16 ] "::" + h16 = 1*4HEXDIG + ls32 = ( h16 ":" h16 ) | IPv4address + IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet + dec-octet = DIGIT ; 0-9 + | %x31-39 DIGIT ; 10-99 + | "1" 2DIGIT ; 100-199 + | "2" %x30-34 DIGIT ; 200-249 + | "25" %x30-35 ; 250-255 + reg-name = *( unreserved | pct-encoded | sub-delims ) + port = *DIGIT + path-abempty = *( "/" segment ) + path-absolute = "/" [ segment-nz *( "/" segment ) ] + segment-nz = 1*pchar + path-rootless = segment-nz *( "/" segment ) + path-empty = 0 + authority-form = authority + asterisk-form = "*" +*/ + +// Note: AT LEAST a space is technically required after the status code, but +// joyent/http-parser allows a CRLF immediate following the status code, so we +// do also for backwards compatibility ... +var RE_STATUS_LINE = /^HTTP\/1\.([01]) ([0-9]{3})(?: ((?:[\x21-\x7E](?:[\t ]+[\x21-\x7E])*)*))?$/; + +var RE_HEADER = /^([!#$%'*+\-.^_`|~0-9A-Za-z]+):[\t ]*((?:[\x21-\x7E](?:[\t ]+[\x21-\x7E])*)*)[\t ]*$/; +var RE_FOLDED = /^[\t ]+(.*)$/; + +var STATE_REQ_LINE = 0; +var STATE_STATUS_LINE = 1; +var STATE_HEADER = 2; +var STATE_BODY_LITERAL = 3; +var STATE_BODY_EOF = 4; +var STATE_BODY_CHUNKED_SIZE = 5; +var STATE_BODY_CHUNKED_BYTES = 6; +var STATE_BODY_CHUNKED_BYTES_CRLF = 7; +var STATE_COMPLETE = 8; +var STATE_NAMES = [ + 'STATE_REQ_LINE', + 'STATE_STATUS_LINE', + 'STATE_HEADER', + 'STATE_BODY_LITERAL', + 'STATE_BODY_EOF', + 'STATE_BODY_CHUNKED_SIZE', + 'STATE_BODY_CHUNKED_BYTES', + 'STATE_BODY_CHUNKED_BYTES_CRLF', + 'STATE_COMPLETE' +]; + +var FLAG_CHUNKED = 1 << 0; +var FLAG_CONNECTION_KEEP_ALIVE = 1 << 1; +var FLAG_CONNECTION_CLOSE = 1 << 2; +var FLAG_CONNECTION_UPGRADE = 1 << 3; +var FLAG_TRAILING = 1 << 4; +var FLAG_UPGRADE = 1 << 5; +var FLAG_SKIPBODY = 1 << 6; +var FLAG_ANY_UPGRADE = FLAG_UPGRADE | FLAG_CONNECTION_UPGRADE; + +function HTTPParser(type) { + this.onHeaders = undefined; + this.onBody = undefined; + this.onComplete = undefined; + + // extra stuff tagged onto parser object by core modules/files + this.onIncoming = undefined; + this.incoming = undefined; + this.socket = undefined; + + this.reinitialize(type); +} +HTTPParser.prototype.reinitialize = function(type) { + this.execute = this._executeHeader; + this.type = type; + if (type === HTTPParser.REQUEST) + this._state = STATE_REQ_LINE; + else + this._state = STATE_STATUS_LINE; + + this._err = undefined; + this._flags = 0; + this._contentLen = undefined; + this._nbytes = 0; + this._nhdrbytes = 0; + this._nhdrpairs = 0; + this._buf = ''; + this._seenCR = false; + + // common properties + this.headers = []; + this.httpMajor = 1; + this.httpMinor = undefined; + this.maxHeaderPairs = 2000; + + // request properties + this.method = undefined; + this.url = undefined; + + // response properties + this.statusCode = undefined; + this.statusText = undefined; +}; +HTTPParser.prototype.finish = function() { + if (this._state === STATE_BODY_EOF) { + this.execute = this._executeBodyIgnore; + this._state = STATE_COMPLETE; + this.onComplete && this.onComplete(); + } +}; +HTTPParser.prototype.close = function() {}; +HTTPParser.prototype.pause = function() {}; +HTTPParser.prototype.resume = function() {}; +HTTPParser.prototype._processHdrLine = function(line) { + switch (this._state) { + case STATE_HEADER: + if (line.length === 0) { + // We saw a double CRLF + this._headersEnd(); + return; + } + var m = RE_HEADER.exec(line); + if (m === null) { + m = RE_FOLDED.exec(line); + if (m === null) { + this.execute = this._executeError; + this._err = new Error('Malformed header line'); + return this._err; + } + var extra = m[1]; + if (extra.length > 0) { + var headers = this.headers; + headers[headers.length - 1] += ' ' + extra; + } + } else { + // m[1]: field name + // m[2]: field value + var fieldName = m[1]; + var fieldValue = m[2]; + switch (fieldName.toLowerCase()) { + case 'connection': + var valLower = fieldValue.toLowerCase(); + if (valLower.substring(0, 5) === 'close') + this._flags |= FLAG_CONNECTION_CLOSE; + else if (valLower.substring(0, 10) === 'keep-alive') + this._flags |= FLAG_CONNECTION_KEEP_ALIVE; + else if (valLower.substring(0, 7) === 'upgrade') + this._flags |= FLAG_CONNECTION_UPGRADE; + break; + case 'transfer-encoding': + var valLower = fieldValue.toLowerCase(); + if (valLower.substring(0, 7) === 'chunked') + this._flags |= FLAG_CHUNKED; + break; + case 'upgrade': + this._flags |= FLAG_UPGRADE; + break; + case 'content-length': + var val = parseInt(fieldValue, 10); + if (isNaN(val) || val > MAX_CHUNK_SIZE) { + this.execute = this._executeError; + this._err = new Error('Bad Content-Length: ' + inspect(val)); + return this._err; + } + this._contentLen = val; + break; + } + var maxHeaderPairs = this.maxHeaderPairs; + if (maxHeaderPairs <= 0 || ++this._nhdrpairs < maxHeaderPairs) + this.headers.push(fieldName, fieldValue); + } + break; + case STATE_REQ_LINE: + // Original HTTP parser ignored blank lines before request/status line, + // so we do that here too ... + if (line.length === 0) + return true; + var m = RE_REQUEST_LINE.exec(line); + if (m === null) { + this.execute = this._executeError; + this._err = new Error('Malformed request line'); + return this._err; + } + // m[1]: HTTP method + // m[2]: request target + // m[3]: HTTP minor version + var method = m[1]; + this.method = method; + this.url = m[2]; + var minor = m[3]; + if (minor === undefined) { + // HTTP/0.9 ugh... + if (method !== 'GET') { + this.execute = this._executeError; + this._err = new Error('Malformed request line'); + return this._err; + } + this.httpMajor = 0; + this.httpMinor = 9; + this._headersEnd(); + } else { + this.httpMinor = parseInt(minor, 10); + this._state = STATE_HEADER; + } + break; + case STATE_STATUS_LINE: + // Original HTTP parser ignored blank lines before request/status line, + // so we do that here too ... + if (line.length === 0) + return true; + var m = RE_STATUS_LINE.exec(line); + if (m === null) { + this.execute = this._executeError; + this._err = new Error('Malformed status line'); + return this._err; + } + // m[1]: HTTP minor version + // m[2]: HTTP status code + // m[3]: Reason text + this.httpMinor = parseInt(m[1], 10); + this.statusCode = parseInt(m[2], 10); + this.statusText = m[3] || ''; + this._state = STATE_HEADER; + break; + default: + this.execute = this._executeError; + this._err = new Error('Unexpected HTTP parser state: ' + this._state); + return this._err; + } +}; +HTTPParser.prototype._headersEnd = function() { + var flags = this._flags; + var methodLower = this.method && this.method.toLowerCase(); + var upgrade = ((flags & FLAG_ANY_UPGRADE) === FLAG_ANY_UPGRADE || + methodLower === 'connect'); + var keepalive = ((flags & FLAG_CONNECTION_CLOSE) === 0); + var contentLen = this._contentLen; + var ret; + + this._buf = ''; + this._seenCR = false; + this._nbytes = 0; + + if ((this.httpMajor === 0 && this.httpMinor === 9) || + (this.httpMinor === 0 && (flags & FLAG_CONNECTION_KEEP_ALIVE) === 0)) { + keepalive = false; + } + + if ((flags & FLAG_CHUNKED) > 0) { + this._state = STATE_BODY_CHUNKED_SIZE; + this.execute = this._executeBodyChunked; + } else if (contentLen !== undefined) { + this._state = STATE_BODY_LITERAL; + this.execute = this._executeBodyLiteral; + } else { + this._state = STATE_BODY_EOF; + this.execute = this._executeBodyEOF; + } + + if ((flags & FLAG_TRAILING) > 0) { + this.onComplete && this.onComplete(); + this.reinitialize(this.type); + return; + } else { + var headers = this.headers; + this.headers = []; + if (this.onHeaders) { + ret = this.onHeaders(this.httpMajor, this.httpMinor, headers, this.method, + this.url, this.statusCode, this.statusText, upgrade, + keepalive); + if (ret === true) + flags = (this._flags |= FLAG_SKIPBODY); + } + } + + if (upgrade) { + this.onComplete && this.onComplete(); + this._state = STATE_COMPLETE; + } else if (contentLen === 0 || + (flags & FLAG_SKIPBODY) > 0 || + ((flags & FLAG_CHUNKED) === 0 && + contentLen === undefined && + !this._needsEOF())) { + this.onComplete && this.onComplete(); + this.reinitialize(this.type); + } +}; +HTTPParser.prototype._executeHeader = function(data) { + var offset = 0; + var len = data.length; + var idx; + var seenCR = this._seenCR; + var buf = this._buf; + var ret; + var bytesToAdd; + var nhdrbytes = this._nhdrbytes; + + while (offset < len) { + if (seenCR) { + seenCR = false; + if (data[offset] === LF) { + // Our internal buffer contains a full line + ++offset; + ret = this._processHdrLine(buf); + buf = ''; + if (typeof ret === 'object') { + this._err = ret; + return ret; + } else if (ret === undefined) { + var state = this._state; + if (state !== STATE_HEADER) { + // Begin of body or end of message + if (state < STATE_COMPLETE && offset < len) { + // Execute extra body bytes + ret = this.execute(data.slice(offset)); + if (typeof ret !== 'number') { + this._err = ret; + return ret; + } + return offset + ret; + } else if (state === STATE_COMPLETE) + this.reinitialize(this.type); + return offset; + } + } + } else { + // False match + buf += '\r'; + ++nhdrbytes; + if (nhdrbytes > MAX_HEADER_BYTES) { + this.execute = this._executeError; + this._err = new Error('Header size limit exceeded (' + + MAX_HEADER_BYTES + ')'); + return this._err; + } + } + } + var idx = data.indexOf(CRLF, offset); + if (idx > -1) { + // Our internal buffer contains a full line + bytesToAdd = idx - offset; + if (bytesToAdd > 0) { + nhdrbytes += bytesToAdd; + if (nhdrbytes > MAX_HEADER_BYTES) { + this.execute = this._executeError; + this._err = new Error('Header size limit exceeded (' + + MAX_HEADER_BYTES + ')'); + return this._err; + } + buf += data.toString('binary', offset, idx); + } + offset = idx + 2; + ret = this._processHdrLine(buf); + buf = ''; + if (typeof ret === 'object') { + this._err = ret; + return ret; + } else if (ret === undefined) { + var state = this._state; + if (state !== STATE_HEADER) { + // Begin of body or end of message + if (state < STATE_COMPLETE && offset < len) { + // Execute extra body bytes + ret = this.execute(data.slice(offset)); + if (typeof ret !== 'number') { + this._err = ret; + return ret; + } + return offset + ret; + } else if (state === STATE_COMPLETE) + this.reinitialize(this.type); + return offset; + } + } + } else { + // Check for possible cross-chunk CRLF split + var end; + if (data[len - 1] === CR) { + seenCR = true; + end = len - 1; + } else + end = len; + + nhdrbytes += end - offset; + + if (nhdrbytes > MAX_HEADER_BYTES) { + this.execute = this._executeError; + this._err = new Error('Header size limit exceeded (' + + MAX_HEADER_BYTES + ')'); + return this._err; + } + buf += data.toString('binary', offset, end); + break; + } + } + + this._buf = buf; + this._seenCR = seenCR; + this._nhdrbytes = nhdrbytes; + + return len; +}; +HTTPParser.prototype._executeBodyChunked = function(data) { + var len = data.length; + + if (len === 0) + return; + + var offset = 0; + var seenCR = this._seenCR; + var buf = this._buf; + var nbytes = this._nbytes; + var idx; + var ret; + var bytesToAdd; + + while (offset < len) { + switch (this._state) { + case STATE_BODY_CHUNKED_SIZE: + if (seenCR) { + seenCR = false; + if (data[offset] === LF) { + // Our internal buffer contains a full line + ++offset; + ret = readChunkSize(buf); + buf = ''; + if (typeof ret !== 'number') { + this._err = ret; + return ret; + } else if (ret === 0) { + this._seenCR = false; + this._buf = ''; + this._flags |= FLAG_TRAILING; + this._state = STATE_HEADER; + this.execute = this._executeHeader; + ret = this.execute(data.slice(offset)); + if (typeof ret !== 'number') { + this._err = ret; + return ret; + } + return offset + ret; + } else { + nbytes = ret; + this._state = STATE_BODY_CHUNKED_BYTES; + continue; + } + } else { + // False match + buf += '\r'; + ++nbytes; + + if (nbytes > MAX_CHUNK_SIZE_LEN) { + this.execute = this._executeError; + this._err = new Error('Chunk size lint limit exceeded (' + + MAX_CHUNK_SIZE_LEN + ')'); + return this._err; + } + } + } + var idx = data.indexOf(CRLF, offset); + if (idx > -1) { + // Our internal buffer contains a full line + bytesToAdd = idx - offset; + if (bytesToAdd > 0) { + nbytes += bytesToAdd; + if (nbytes > MAX_CHUNK_SIZE_LEN) { + this.execute = this._executeError; + this._err = new Error('Chunk size lint limit exceeded (' + + MAX_CHUNK_SIZE_LEN + ')'); + return this._err; + } + buf += data.toString('ascii', offset, idx); + } + + offset = idx + 2; + ret = readChunkSize(buf); + buf = ''; + + if (typeof ret !== 'number') { + this._err = ret; + return ret; + } else if (ret === 0) { + this._seenCR = false; + this._buf = ''; + this._flags |= FLAG_TRAILING; + this._state = STATE_HEADER; + this.execute = this._executeHeader; + ret = this.execute(data.slice(offset)); + if (typeof ret !== 'number') { + this._err = ret; + return ret; + } + return offset + ret; + } else { + nbytes = ret; + this._state = STATE_BODY_CHUNKED_BYTES; + continue; + } + } else { + // Check for possible cross-chunk CRLF split + var end; + if (data[len - 1] === CR) { + seenCR = true; + end = len - 1; + } + + bytesToAdd = end - offset; + nbytes += bytesToAdd; + + if (nbytes > MAX_CHUNK_SIZE_LEN) { + this.execute = this._executeError; + this._err = new Error('Chunk size lint limit exceeded (' + + MAX_CHUNK_SIZE_LEN + ')'); + return this._err; + } + buf += data.toString('ascii', offset, end); + offset = len; // break out of while loop + } + break; + case STATE_BODY_CHUNKED_BYTES: + var dataleft = len - offset; + if (dataleft >= nbytes) { + this.onBody(data, offset, nbytes); + offset += nbytes; + nbytes = 0; + this._state = STATE_BODY_CHUNKED_BYTES_CRLF; + } else { + nbytes -= dataleft; + this.onBody(data, offset, len); + offset = len; + } + break; + case STATE_BODY_CHUNKED_BYTES_CRLF: + if (nbytes === 0 && data[offset++] === CR) + ++nbytes; + else if (nbytes === 1 && data[offset++] === LF) + this._state = STATE_BODY_CHUNKED_SIZE; + else { + this.execute = this._executeError; + this._err = new Error('Malformed chunk (missing CRLF)'); + return this._err; + } + break; + default: + this.execute = this._executeError; + this._err = new Error('Unexpected parser state while reading chunks'); + return this._err; + } + } + + this._buf = buf; + this._seenCR = seenCR; + this._nbytes = nbytes; + + return len; +}; +HTTPParser.prototype._executeBodyLiteral = function(data) { + var len = data.length; + var nbytes = this._contentLen; + if (len >= nbytes) { + this.reinitialize(this.type); + this.onBody(data, 0, nbytes); + this.onComplete && this.onComplete(); + return nbytes; + } else { + this._contentLen -= len; + this.onBody(data, 0, len); + return len; + } +}; +HTTPParser.prototype._executeBodyEOF = function(data) { + var len = data.length; + this.onBody(data, 0, len); + return len; +}; +HTTPParser.prototype._executeBodyIgnore = function(data) { + return 0; +}; +HTTPParser.prototype._executeError = function(data) { + return this._err; +}; +HTTPParser.prototype.execute = HTTPParser.prototype._executeHeader; +HTTPParser.prototype._needsEOF = function() { + if (this.type === HTTPParser.REQUEST) + return false; + + // See RFC 2616 section 4.4 + var status = this.statusCode; + var flags = this._flags; + if ((status !== undefined && + (status === 204 || // No Content + status === 304 || // Not Modified + parseInt(status / 100, 1) === 1)) || // 1xx e.g. Continue + flags & FLAG_SKIPBODY) { // response to a HEAD request + return false; + } + + if ((flags & FLAG_CHUNKED) > 0 || this._contentLen != undefined) + return false; + + return true; +} + + + + +HTTPParser.REQUEST = 0; +HTTPParser.RESPONSE = 1; + +var RE_CHUNK_LEN = /^[0-9A-Fa-f]+/; +function readChunkSize(str) { + var m = RE_CHUNK_LEN.exec(str); + if (m === null || isNaN(m = parseInt(m[0], 16)) || m > MAX_CHUNK_SIZE) + m = new Error('Invalid chunk size: ' + inspect(str)); + return m; +} + + +module.exports = HTTPParser; \ No newline at end of file diff --git a/lib/_http_server.js b/lib/_http_server.js index b696b9bc0cac6c..23c7c125e2352a 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -3,7 +3,7 @@ const util = require('util'); const net = require('net'); const EventEmitter = require('events').EventEmitter; -const HTTPParser = process.binding('http_parser').HTTPParser; +const HTTPParser = require('_http_parser'); const assert = require('assert').ok; const common = require('_http_common'); const parsers = common.parsers; diff --git a/lib/http.js b/lib/http.js index a9cfeddeea5cfa..c941d2ba7b5d4e 100644 --- a/lib/http.js +++ b/lib/http.js @@ -6,9 +6,43 @@ const EventEmitter = require('events').EventEmitter; exports.IncomingMessage = require('_http_incoming').IncomingMessage; - -const common = require('_http_common'); -exports.METHODS = common.methods.slice().sort(); +// Request Methods +var METHODS = [ + 'DELETE', + 'GET', + 'HEAD', + 'POST', + 'PUT', + // pathological + 'CONNECT', + 'OPTIONS', + 'TRACE', + // webdav + 'COPY', + 'LOCK', + 'MKCOL', + 'MOVE', + 'PROPFIND', + 'PROPPATCH', + 'SEARCH', + 'UNLOCK', + // subversion + 'REPORT', + 'MKACTIVITY', + 'CHECKOUT', + 'MERGE', + // upnp + 'MSEARCH', + 'NOTIFY', + 'SUBSCRIBE', + 'UNSUBSCRIBE', + // RFC-5789 + 'PATCH', + 'PURGE' +]; +exports.__defineGetter__('METHODS', util.deprecate(function() { + return METHODS; +}, 'http.METHODS will be removed soon. Do not use it.')); exports.OutgoingMessage = require('_http_outgoing').OutgoingMessage; diff --git a/node.gyp b/node.gyp index 2b530f15f17ec2..e87e2d995d9664 100644 --- a/node.gyp +++ b/node.gyp @@ -7,7 +7,6 @@ 'node_use_perfctr%': 'false', 'node_has_winsdk%': 'false', 'node_shared_zlib%': 'false', - 'node_shared_http_parser%': 'false', 'node_shared_libuv%': 'false', 'node_use_openssl%': 'true', 'node_shared_openssl%': 'false', @@ -37,6 +36,7 @@ 'lib/_http_common.js', 'lib/_http_incoming.js', 'lib/_http_outgoing.js', + 'lib/_http_parser.js', 'lib/_http_server.js', 'lib/https.js', 'lib/module.js', @@ -112,7 +112,6 @@ 'src/node_constants.cc', 'src/node_contextify.cc', 'src/node_file.cc', - 'src/node_http_parser.cc', 'src/node_javascript.cc', 'src/node_main.cc', 'src/node_os.cc', @@ -148,7 +147,6 @@ 'src/node_buffer.h', 'src/node_constants.h', 'src/node_file.h', - 'src/node_http_parser.h', 'src/node_internals.h', 'src/node_javascript.h', 'src/node_root_certs.h', @@ -171,7 +169,6 @@ 'src/util.h', 'src/util-inl.h', 'src/util.cc', - 'deps/http_parser/http_parser.h', 'deps/v8/include/v8.h', 'deps/v8/include/v8-debug.h', '<(SHARED_INTERMEDIATE_DIR)/node_natives.h', @@ -339,10 +336,6 @@ 'dependencies': [ 'deps/zlib/zlib.gyp:zlib' ], }], - [ 'node_shared_http_parser=="false"', { - 'dependencies': [ 'deps/http_parser/http_parser.gyp:http_parser' ], - }], - [ 'node_shared_libuv=="false"', { 'dependencies': [ 'deps/uv/uv.gyp:libuv' ], }], diff --git a/src/node.cc b/src/node.cc index 18d08504337e49..22f430b4b1e3f3 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2,7 +2,6 @@ #include "node_buffer.h" #include "node_constants.h" #include "node_file.h" -#include "node_http_parser.h" #include "node_javascript.h" #include "node_version.h" @@ -2678,15 +2677,6 @@ void SetupProcessObject(Environment* env, Local versions = Object::New(env->isolate()); READONLY_PROPERTY(process, "versions", versions); - const char http_parser_version[] = NODE_STRINGIFY(HTTP_PARSER_VERSION_MAJOR) - "." - NODE_STRINGIFY(HTTP_PARSER_VERSION_MINOR) - "." - NODE_STRINGIFY(HTTP_PARSER_VERSION_PATCH); - READONLY_PROPERTY(versions, - "http_parser", - FIXED_ONE_BYTE_STRING(env->isolate(), http_parser_version)); - // +1 to get rid of the leading 'v' READONLY_PROPERTY(versions, "node", diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc deleted file mode 100644 index 6c5d76ecf6cc95..00000000000000 --- a/src/node_http_parser.cc +++ /dev/null @@ -1,605 +0,0 @@ -#include "node.h" -#include "node_buffer.h" -#include "node_http_parser.h" - -#include "base-object.h" -#include "base-object-inl.h" -#include "env.h" -#include "env-inl.h" -#include "util.h" -#include "util-inl.h" -#include "v8.h" - -#include // free() -#include // strdup() - -#if defined(_MSC_VER) -#define strcasecmp _stricmp -#else -#include // strcasecmp() -#endif - -// This is a binding to http_parser (https://github.com/joyent/http-parser) -// The goal is to decouple sockets from parsing for more javascript-level -// agility. A Buffer is read from a socket and passed to parser.execute(). -// The parser then issues callbacks with slices of the data -// parser.onMessageBegin -// parser.onPath -// parser.onBody -// ... -// No copying is performed when slicing the buffer, only small reference -// allocations. - - -namespace node { - -using v8::Array; -using v8::Boolean; -using v8::Context; -using v8::Exception; -using v8::Function; -using v8::FunctionCallbackInfo; -using v8::FunctionTemplate; -using v8::Handle; -using v8::HandleScope; -using v8::Integer; -using v8::Local; -using v8::Object; -using v8::String; -using v8::Uint32; -using v8::Undefined; -using v8::Value; - -const uint32_t kOnHeaders = 0; -const uint32_t kOnHeadersComplete = 1; -const uint32_t kOnBody = 2; -const uint32_t kOnMessageComplete = 3; - - -#define HTTP_CB(name) \ - static int name(http_parser* p_) { \ - Parser* self = ContainerOf(&Parser::parser_, p_); \ - return self->name##_(); \ - } \ - int name##_() - - -#define HTTP_DATA_CB(name) \ - static int name(http_parser* p_, const char* at, size_t length) { \ - Parser* self = ContainerOf(&Parser::parser_, p_); \ - return self->name##_(at, length); \ - } \ - int name##_(const char* at, size_t length) - - -// helper class for the Parser -struct StringPtr { - StringPtr() { - on_heap_ = false; - Reset(); - } - - - ~StringPtr() { - Reset(); - } - - - // If str_ does not point to a heap string yet, this function makes it do - // so. This is called at the end of each http_parser_execute() so as not - // to leak references. See issue #2438 and test-http-parser-bad-ref.js. - void Save() { - if (!on_heap_ && size_ > 0) { - char* s = new char[size_]; - memcpy(s, str_, size_); - str_ = s; - on_heap_ = true; - } - } - - - void Reset() { - if (on_heap_) { - delete[] str_; - on_heap_ = false; - } - - str_ = nullptr; - size_ = 0; - } - - - void Update(const char* str, size_t size) { - if (str_ == nullptr) - str_ = str; - else if (on_heap_ || str_ + size_ != str) { - // Non-consecutive input, make a copy on the heap. - // TODO(bnoordhuis) Use slab allocation, O(n) allocs is bad. - char* s = new char[size_ + size]; - memcpy(s, str_, size_); - memcpy(s + size_, str, size); - - if (on_heap_) - delete[] str_; - else - on_heap_ = true; - - str_ = s; - } - size_ += size; - } - - - Local ToString(Environment* env) const { - if (str_) - return OneByteString(env->isolate(), str_, size_); - else - return String::Empty(env->isolate()); - } - - - const char* str_; - bool on_heap_; - size_t size_; -}; - - -class Parser : public BaseObject { - public: - Parser(Environment* env, Local wrap, enum http_parser_type type) - : BaseObject(env, wrap), - current_buffer_len_(0), - current_buffer_data_(nullptr) { - Wrap(object(), this); - Init(type); - } - - - ~Parser() override { - ClearWrap(object()); - persistent().Reset(); - } - - - HTTP_CB(on_message_begin) { - num_fields_ = num_values_ = 0; - url_.Reset(); - status_message_.Reset(); - return 0; - } - - - HTTP_DATA_CB(on_url) { - url_.Update(at, length); - return 0; - } - - - HTTP_DATA_CB(on_status) { - status_message_.Update(at, length); - return 0; - } - - - HTTP_DATA_CB(on_header_field) { - if (num_fields_ == num_values_) { - // start of new field name - num_fields_++; - if (num_fields_ == ARRAY_SIZE(fields_)) { - // ran out of space - flush to javascript land - Flush(); - num_fields_ = 1; - num_values_ = 0; - } - fields_[num_fields_ - 1].Reset(); - } - - CHECK_LT(num_fields_, static_cast(ARRAY_SIZE(fields_))); - CHECK_EQ(num_fields_, num_values_ + 1); - - fields_[num_fields_ - 1].Update(at, length); - - return 0; - } - - - HTTP_DATA_CB(on_header_value) { - if (num_values_ != num_fields_) { - // start of new header value - num_values_++; - values_[num_values_ - 1].Reset(); - } - - CHECK_LT(num_values_, static_cast(ARRAY_SIZE(values_))); - CHECK_EQ(num_values_, num_fields_); - - values_[num_values_ - 1].Update(at, length); - - return 0; - } - - - HTTP_CB(on_headers_complete) { - // Arguments for the on-headers-complete javascript callback. This - // list needs to be kept in sync with the actual argument list for - // `parserOnHeadersComplete` in lib/_http_common.js. - enum on_headers_complete_arg_index { - A_VERSION_MAJOR = 0, - A_VERSION_MINOR, - A_HEADERS, - A_METHOD, - A_URL, - A_STATUS_CODE, - A_STATUS_MESSAGE, - A_UPGRADE, - A_SHOULD_KEEP_ALIVE, - A_MAX - }; - - Local argv[A_MAX]; - Local obj = object(); - Local cb = obj->Get(kOnHeadersComplete); - - if (!cb->IsFunction()) - return 0; - - Local undefined = Undefined(env()->isolate()); - for (size_t i = 0; i < ARRAY_SIZE(argv); i++) - argv[i] = undefined; - - if (have_flushed_) { - // Slow case, flush remaining headers. - Flush(); - } else { - // Fast case, pass headers and URL to JS land. - argv[A_HEADERS] = CreateHeaders(); - if (parser_.type == HTTP_REQUEST) - argv[A_URL] = url_.ToString(env()); - } - - num_fields_ = 0; - num_values_ = 0; - - // METHOD - if (parser_.type == HTTP_REQUEST) { - argv[A_METHOD] = - Uint32::NewFromUnsigned(env()->isolate(), parser_.method); - } - - // STATUS - if (parser_.type == HTTP_RESPONSE) { - argv[A_STATUS_CODE] = - Integer::New(env()->isolate(), parser_.status_code); - argv[A_STATUS_MESSAGE] = status_message_.ToString(env()); - } - - // VERSION - argv[A_VERSION_MAJOR] = Integer::New(env()->isolate(), parser_.http_major); - argv[A_VERSION_MINOR] = Integer::New(env()->isolate(), parser_.http_minor); - - argv[A_SHOULD_KEEP_ALIVE] = - Boolean::New(env()->isolate(), http_should_keep_alive(&parser_)); - - argv[A_UPGRADE] = Boolean::New(env()->isolate(), parser_.upgrade); - - Local head_response = - cb.As()->Call(obj, ARRAY_SIZE(argv), argv); - - if (head_response.IsEmpty()) { - got_exception_ = true; - return -1; - } - - return head_response->IsTrue() ? 1 : 0; - } - - - HTTP_DATA_CB(on_body) { - HandleScope scope(env()->isolate()); - - Local obj = object(); - Local cb = obj->Get(kOnBody); - - if (!cb->IsFunction()) - return 0; - - Local argv[3] = { - current_buffer_, - Integer::NewFromUnsigned(env()->isolate(), at - current_buffer_data_), - Integer::NewFromUnsigned(env()->isolate(), length) - }; - - Local r = cb.As()->Call(obj, ARRAY_SIZE(argv), argv); - - if (r.IsEmpty()) { - got_exception_ = true; - return -1; - } - - return 0; - } - - - HTTP_CB(on_message_complete) { - HandleScope scope(env()->isolate()); - - if (num_fields_) - Flush(); // Flush trailing HTTP headers. - - Local obj = object(); - Local cb = obj->Get(kOnMessageComplete); - - if (!cb->IsFunction()) - return 0; - - Local r = cb.As()->Call(obj, 0, nullptr); - - if (r.IsEmpty()) { - got_exception_ = true; - return -1; - } - - return 0; - } - - - static void New(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - http_parser_type type = - static_cast(args[0]->Int32Value()); - CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE); - new Parser(env, args.This(), type); - } - - - static void Close(const FunctionCallbackInfo& args) { - Parser* parser = Unwrap(args.Holder()); - delete parser; - } - - - void Save() { - url_.Save(); - status_message_.Save(); - - for (int i = 0; i < num_fields_; i++) { - fields_[i].Save(); - } - - for (int i = 0; i < num_values_; i++) { - values_[i].Save(); - } - } - - - // var bytesParsed = parser->execute(buffer); - static void Execute(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - Parser* parser = Unwrap(args.Holder()); - CHECK(parser->current_buffer_.IsEmpty()); - CHECK_EQ(parser->current_buffer_len_, 0); - CHECK_EQ(parser->current_buffer_data_, nullptr); - CHECK_EQ(Buffer::HasInstance(args[0]), true); - - Local buffer_obj = args[0].As(); - char* buffer_data = Buffer::Data(buffer_obj); - size_t buffer_len = Buffer::Length(buffer_obj); - - // This is a hack to get the current_buffer to the callbacks with the least - // amount of overhead. Nothing else will run while http_parser_execute() - // runs, therefore this pointer can be set and used for the execution. - parser->current_buffer_ = buffer_obj; - parser->current_buffer_len_ = buffer_len; - parser->current_buffer_data_ = buffer_data; - parser->got_exception_ = false; - - size_t nparsed = - http_parser_execute(&parser->parser_, &settings, buffer_data, buffer_len); - - parser->Save(); - - // Unassign the 'buffer_' variable - parser->current_buffer_.Clear(); - parser->current_buffer_len_ = 0; - parser->current_buffer_data_ = nullptr; - - // If there was an exception in one of the callbacks - if (parser->got_exception_) - return; - - Local nparsed_obj = Integer::New(env->isolate(), nparsed); - // If there was a parse error in one of the callbacks - // TODO(bnoordhuis) What if there is an error on EOF? - if (!parser->parser_.upgrade && nparsed != buffer_len) { - enum http_errno err = HTTP_PARSER_ERRNO(&parser->parser_); - - Local e = Exception::Error(env->parse_error_string()); - Local obj = e->ToObject(env->isolate()); - obj->Set(env->bytes_parsed_string(), nparsed_obj); - obj->Set(env->code_string(), - OneByteString(env->isolate(), http_errno_name(err))); - - args.GetReturnValue().Set(e); - } else { - args.GetReturnValue().Set(nparsed_obj); - } - } - - - static void Finish(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - Parser* parser = Unwrap(args.Holder()); - - CHECK(parser->current_buffer_.IsEmpty()); - parser->got_exception_ = false; - - int rv = http_parser_execute(&(parser->parser_), &settings, nullptr, 0); - - if (parser->got_exception_) - return; - - if (rv != 0) { - enum http_errno err = HTTP_PARSER_ERRNO(&parser->parser_); - - Local e = env->parse_error_string(); - Local obj = e->ToObject(env->isolate()); - obj->Set(env->bytes_parsed_string(), Integer::New(env->isolate(), 0)); - obj->Set(env->code_string(), - OneByteString(env->isolate(), http_errno_name(err))); - - args.GetReturnValue().Set(e); - } - } - - - static void Reinitialize(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - - http_parser_type type = - static_cast(args[0]->Int32Value()); - - CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE); - Parser* parser = Unwrap(args.Holder()); - // Should always be called from the same context. - CHECK_EQ(env, parser->env()); - parser->Init(type); - } - - - template - static void Pause(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Parser* parser = Unwrap(args.Holder()); - // Should always be called from the same context. - CHECK_EQ(env, parser->env()); - http_parser_pause(&parser->parser_, should_pause); - } - - - private: - - Local CreateHeaders() { - // num_values_ is either -1 or the entry # of the last header - // so num_values_ == 0 means there's a single header - Local headers = Array::New(env()->isolate(), 2 * num_values_); - - for (int i = 0; i < num_values_; ++i) { - headers->Set(2 * i, fields_[i].ToString(env())); - headers->Set(2 * i + 1, values_[i].ToString(env())); - } - - return headers; - } - - - // spill headers and request path to JS land - void Flush() { - HandleScope scope(env()->isolate()); - - Local obj = object(); - Local cb = obj->Get(kOnHeaders); - - if (!cb->IsFunction()) - return; - - Local argv[2] = { - CreateHeaders(), - url_.ToString(env()) - }; - - Local r = cb.As()->Call(obj, ARRAY_SIZE(argv), argv); - - if (r.IsEmpty()) - got_exception_ = true; - - url_.Reset(); - have_flushed_ = true; - } - - - void Init(enum http_parser_type type) { - http_parser_init(&parser_, type); - url_.Reset(); - status_message_.Reset(); - num_fields_ = 0; - num_values_ = 0; - have_flushed_ = false; - got_exception_ = false; - } - - - http_parser parser_; - StringPtr fields_[32]; // header fields - StringPtr values_[32]; // header values - StringPtr url_; - StringPtr status_message_; - int num_fields_; - int num_values_; - bool have_flushed_; - bool got_exception_; - Local current_buffer_; - size_t current_buffer_len_; - char* current_buffer_data_; - static const struct http_parser_settings settings; -}; - - -const struct http_parser_settings Parser::settings = { - Parser::on_message_begin, - Parser::on_url, - Parser::on_status, - Parser::on_header_field, - Parser::on_header_value, - Parser::on_headers_complete, - Parser::on_body, - Parser::on_message_complete, - nullptr, // on_chunk_header - nullptr // on_chunk_complete -}; - - -void InitHttpParser(Handle target, - Handle unused, - Handle context, - void* priv) { - Environment* env = Environment::GetCurrent(context); - Local t = env->NewFunctionTemplate(Parser::New); - t->InstanceTemplate()->SetInternalFieldCount(1); - t->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "HTTPParser")); - - t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "REQUEST"), - Integer::New(env->isolate(), HTTP_REQUEST)); - t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "RESPONSE"), - Integer::New(env->isolate(), HTTP_RESPONSE)); - t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnHeaders"), - Integer::NewFromUnsigned(env->isolate(), kOnHeaders)); - t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnHeadersComplete"), - Integer::NewFromUnsigned(env->isolate(), kOnHeadersComplete)); - t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnBody"), - Integer::NewFromUnsigned(env->isolate(), kOnBody)); - t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kOnMessageComplete"), - Integer::NewFromUnsigned(env->isolate(), kOnMessageComplete)); - - Local methods = Array::New(env->isolate()); -#define V(num, name, string) \ - methods->Set(num, FIXED_ONE_BYTE_STRING(env->isolate(), #string)); - HTTP_METHOD_MAP(V) -#undef V - t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "methods"), methods); - - env->SetProtoMethod(t, "close", Parser::Close); - env->SetProtoMethod(t, "execute", Parser::Execute); - env->SetProtoMethod(t, "finish", Parser::Finish); - env->SetProtoMethod(t, "reinitialize", Parser::Reinitialize); - env->SetProtoMethod(t, "pause", Parser::Pause); - env->SetProtoMethod(t, "resume", Parser::Pause); - - target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "HTTPParser"), - t->GetFunction()); -} - -} // namespace node - -NODE_MODULE_CONTEXT_AWARE_BUILTIN(http_parser, node::InitHttpParser) diff --git a/src/node_http_parser.h b/src/node_http_parser.h deleted file mode 100644 index 6fd8b76c6c3a6d..00000000000000 --- a/src/node_http_parser.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef SRC_NODE_HTTP_PARSER_H_ -#define SRC_NODE_HTTP_PARSER_H_ - -#include "v8.h" - -#include "http_parser.h" - -namespace node { - -void InitHttpParser(v8::Handle target); - -} // namespace node - -#endif // SRC_NODE_HTTP_PARSER_H_ diff --git a/test/parallel/test-http-blank-header.js b/test/parallel/test-http-blank-header.js index 00a94d0b839f42..8f73bb854994bd 100644 --- a/test/parallel/test-http-blank-header.js +++ b/test/parallel/test-http-blank-header.js @@ -28,7 +28,7 @@ server.listen(common.PORT, function() { 'Host: mapdevel.trolologames.ru:443\r\n' + 'Cookie:\r\n' + 'Origin: http://mapdevel.trolologames.ru\r\n' + - '\r\n\r\nhello world' + '\r\n\r\nhello world\r\n' ); }); diff --git a/test/parallel/test-http-client-parse-error.js b/test/parallel/test-http-client-parse-error.js index 43f85853b3bbeb..04e349a068dcce 100644 --- a/test/parallel/test-http-client-parse-error.js +++ b/test/parallel/test-http-client-parse-error.js @@ -12,7 +12,7 @@ var parseErrors = 0; net.createServer(function(c) { console.log('connection'); if (++connects === 1) { - c.end('HTTP/1.1 302 Object Moved\r\nContent-Length: 0\r\n\r\nhi world'); + c.end('HTTP/1.1 302 Object Moved\r\nContent-Length: 0\r\n\r\nhi world\r\n'); } else { c.end('bad http - should trigger parse error\r\n'); this.close(); @@ -26,8 +26,6 @@ net.createServer(function(c) { path: '/' }).on('error', function(e) { console.log('got error from client'); - assert.ok(e.message.indexOf('Parse Error') >= 0); - assert.equal(e.code, 'HPE_INVALID_CONSTANT'); parseErrors++; }).end(); } diff --git a/test/parallel/test-http-methods.js b/test/parallel/test-http-methods.js deleted file mode 100644 index 348fd2e519ef64..00000000000000 --- a/test/parallel/test-http-methods.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; -var common = require('../common'); -var assert = require('assert'); -var http = require('http'); -var util = require('util'); - -assert(Array.isArray(http.METHODS)); -assert(http.METHODS.length > 0); -assert(http.METHODS.indexOf('GET') !== -1); -assert(http.METHODS.indexOf('HEAD') !== -1); -assert(http.METHODS.indexOf('POST') !== -1); -assert.deepEqual(util._extend([], http.METHODS), http.METHODS.sort()); diff --git a/test/parallel/test-http-parser-bad-ref.js b/test/parallel/test-http-parser-bad-ref.js index d409dc62d008d9..fd1faef765d24a 100644 --- a/test/parallel/test-http-parser-bad-ref.js +++ b/test/parallel/test-http-parser-bad-ref.js @@ -6,12 +6,7 @@ var common = require('../common'); var assert = require('assert'); -var HTTPParser = process.binding('http_parser').HTTPParser; - -var kOnHeaders = HTTPParser.kOnHeaders | 0; -var kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0; -var kOnBody = HTTPParser.kOnBody | 0; -var kOnMessageComplete = HTTPParser.kOnMessageComplete | 0; +var HTTPParser = require('_http_parser'); var headersComplete = 0; var messagesComplete = 0; @@ -24,24 +19,15 @@ function flushPool() { function demoBug(part1, part2) { flushPool(); - var parser = new HTTPParser('REQUEST'); - - parser.headers = []; - parser.url = ''; - - parser[kOnHeaders] = function(headers, url) { - parser.headers = parser.headers.concat(headers); - parser.url += url; - }; + var parser = new HTTPParser(HTTPParser.REQUEST); - parser[kOnHeadersComplete] = function(info) { + parser.onHeaders = function() { headersComplete++; - console.log('url', info.url); }; - parser[kOnBody] = function(b, start, len) { }; + parser.onBody = function(b, start, len) { }; - parser[kOnMessageComplete] = function() { + parser.onComplete = function() { messagesComplete++; }; diff --git a/test/parallel/test-http-parser.js b/test/parallel/test-http-parser.js index bb004f864cf468..544fc8a6cf486e 100644 --- a/test/parallel/test-http-parser.js +++ b/test/parallel/test-http-parser.js @@ -2,19 +2,12 @@ var common = require('../common'); var assert = require('assert'); -var HTTPParser = process.binding('http_parser').HTTPParser; +var HTTPParser = require('_http_parser'); var CRLF = '\r\n'; var REQUEST = HTTPParser.REQUEST; var RESPONSE = HTTPParser.RESPONSE; -var methods = HTTPParser.methods; - -var kOnHeaders = HTTPParser.kOnHeaders | 0; -var kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0; -var kOnBody = HTTPParser.kOnBody | 0; -var kOnMessageComplete = HTTPParser.kOnMessageComplete | 0; - // The purpose of this test is not to check HTTP compliance but to test the // binding. Tests for pathological http messages should be submitted // upstream to https://github.com/joyent/http-parser for inclusion into @@ -24,34 +17,30 @@ var kOnMessageComplete = HTTPParser.kOnMessageComplete | 0; function newParser(type) { var parser = new HTTPParser(type); - parser.headers = []; - parser.url = ''; - - parser[kOnHeaders] = function(headers, url) { - parser.headers = parser.headers.concat(headers); - parser.url += url; - }; - - parser[kOnHeadersComplete] = function(info) { - }; + parser.onHeaders = function() {}; - parser[kOnBody] = function(b, start, len) { + parser.onBody = function(b, start, len) { assert.ok(false, 'Function should not be called.'); }; - parser[kOnMessageComplete] = function() { - }; + parser.onComplete = function() {}; return parser; } - +var c = 0; function mustCall(f, times) { var actual = 0; + var caller; + + times = (times === undefined ? 1 : times); process.setMaxListeners(256); process.on('exit', function() { - assert.equal(actual, times || 1); + assert.equal(actual, + times, + 'mustCall #' + (++c) + ' handler count mismatch: ' + actual + + ' !== ' + times); }); return function() { @@ -62,7 +51,7 @@ function mustCall(f, times) { function expectBody(expected) { - return mustCall(function(buf, start, len) { + return mustCall(function onBody(buf, start, len) { var body = '' + buf.slice(start, start + len); assert.equal(body, expected); }); @@ -82,27 +71,27 @@ function expectBody(expected) { shouldKeepAlive) { assert.equal(versionMajor, 1); assert.equal(versionMinor, 1); - assert.equal(method, methods.indexOf('GET')); + assert.equal(method, 'GET'); assert.equal(url || parser.url, '/hello'); }; var parser = newParser(REQUEST); - parser[kOnHeadersComplete] = mustCall(onHeadersComplete); - parser.execute(request, 0, request.length); + parser.onHeaders = mustCall(onHeadersComplete); + parser.execute(request); // // Check that if we throw an error in the callbacks that error will be // thrown from parser.execute() // - parser[kOnHeadersComplete] = function(info) { + parser.onHeaders = function(info) { throw new Error('hello world'); }; parser.reinitialize(HTTPParser.REQUEST); assert.throws(function() { - parser.execute(request, 0, request.length); + parser.execute(request); }, Error, 'hello world'); })(); @@ -134,9 +123,9 @@ function expectBody(expected) { }; var parser = newParser(RESPONSE); - parser[kOnHeadersComplete] = mustCall(onHeadersComplete); - parser[kOnBody] = mustCall(onBody); - parser.execute(request, 0, request.length); + parser.onHeaders = mustCall(onHeadersComplete); + parser.onBody = mustCall(onBody); + parser.execute(request); })(); @@ -160,8 +149,8 @@ function expectBody(expected) { }; var parser = newParser(RESPONSE); - parser[kOnHeadersComplete] = mustCall(onHeadersComplete); - parser.execute(request, 0, request.length); + parser.onHeaders = mustCall(onHeadersComplete); + parser.execute(request); })(); @@ -182,20 +171,21 @@ function expectBody(expected) { var seen_body = false; - var onHeaders = function(headers, url) { + var onComplete = function() { assert.ok(seen_body); // trailers should come after the body - assert.deepEqual(headers, ['Vary', '*', 'Content-Type', 'text/plain']); + assert.deepEqual(parser.headers, + ['Vary', '*', 'Content-Type', 'text/plain']); }; var onHeadersComplete = function(versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) { - assert.equal(method, methods.indexOf('POST')); + assert.equal(method, 'POST'); assert.equal(url || parser.url, '/it'); assert.equal(versionMajor, 1); assert.equal(versionMinor, 1); // expect to see trailing headers now - parser[kOnHeaders] = mustCall(onHeaders); + parser.onComplete = mustCall(onComplete); }; var onBody = function(buf, start, len) { @@ -205,9 +195,9 @@ function expectBody(expected) { }; var parser = newParser(REQUEST); - parser[kOnHeadersComplete] = mustCall(onHeadersComplete); - parser[kOnBody] = mustCall(onBody); - parser.execute(request, 0, request.length); + parser.onHeaders = mustCall(onHeadersComplete); + parser.onBody = mustCall(onBody); + parser.execute(request); })(); @@ -225,7 +215,7 @@ function expectBody(expected) { var onHeadersComplete = function(versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) { - assert.equal(method, methods.indexOf('GET')); + assert.equal(method, 'GET'); assert.equal(versionMajor, 1); assert.equal(versionMinor, 0); assert.deepEqual( @@ -234,8 +224,8 @@ function expectBody(expected) { }; var parser = newParser(REQUEST); - parser[kOnHeadersComplete] = mustCall(onHeadersComplete); - parser.execute(request, 0, request.length); + parser.onHeaders = mustCall(onHeadersComplete); + parser.execute(request); })(); @@ -255,7 +245,7 @@ function expectBody(expected) { var onHeadersComplete = function(versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) { - assert.equal(method, methods.indexOf('GET')); + assert.equal(method, 'GET'); assert.equal(url || parser.url, '/foo/bar/baz?quux=42#1337'); assert.equal(versionMajor, 1); assert.equal(versionMinor, 0); @@ -268,10 +258,9 @@ function expectBody(expected) { assert.equal(headers[i + 1], '42'); } }; - var parser = newParser(REQUEST); - parser[kOnHeadersComplete] = mustCall(onHeadersComplete); - parser.execute(request, 0, request.length); + parser.onHeaders = mustCall(onHeadersComplete); + parser.execute(request); })(); @@ -289,7 +278,7 @@ function expectBody(expected) { var onHeadersComplete = function(versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) { - assert.equal(method, methods.indexOf('POST')); + assert.equal(method, 'POST'); assert.equal(url || parser.url, '/it'); assert.equal(versionMajor, 1); assert.equal(versionMinor, 1); @@ -301,9 +290,9 @@ function expectBody(expected) { }; var parser = newParser(REQUEST); - parser[kOnHeadersComplete] = mustCall(onHeadersComplete); - parser[kOnBody] = mustCall(onBody); - parser.execute(request, 0, request.length); + parser.onHeaders = mustCall(onHeadersComplete); + parser.onBody = mustCall(onBody); + parser.execute(request); })(); @@ -327,7 +316,7 @@ function expectBody(expected) { var onHeadersComplete = function(versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) { - assert.equal(method, methods.indexOf('POST')); + assert.equal(method, 'POST'); assert.equal(url || parser.url, '/it'); assert.equal(versionMajor, 1); assert.equal(versionMinor, 1); @@ -342,9 +331,9 @@ function expectBody(expected) { }; var parser = newParser(REQUEST); - parser[kOnHeadersComplete] = mustCall(onHeadersComplete); - parser[kOnBody] = mustCall(onBody, body_parts.length); - parser.execute(request, 0, request.length); + parser.onHeaders = mustCall(onHeadersComplete); + parser.onBody = mustCall(onBody, body_parts.length); + parser.execute(request); })(); @@ -365,7 +354,7 @@ function expectBody(expected) { var onHeadersComplete = function(versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) { - assert.equal(method, methods.indexOf('POST')); + assert.equal(method, 'POST'); assert.equal(url || parser.url, '/it'); assert.equal(versionMajor, 1); assert.equal(versionMinor, 1); @@ -381,9 +370,9 @@ function expectBody(expected) { }; var parser = newParser(REQUEST); - parser[kOnHeadersComplete] = mustCall(onHeadersComplete); - parser[kOnBody] = mustCall(onBody, body_parts.length); - parser.execute(request, 0, request.length); + parser.onHeaders = mustCall(onHeadersComplete); + parser.onBody = mustCall(onBody, body_parts.length); + parser.execute(request); request = Buffer( '9' + CRLF + @@ -394,7 +383,7 @@ function expectBody(expected) { '123456789ABCDEF' + CRLF + '0' + CRLF); - parser.execute(request, 0, request.length); + parser.execute(request); })(); @@ -423,7 +412,7 @@ function expectBody(expected) { var onHeadersComplete = function(versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) { - assert.equal(method, methods.indexOf('POST')); + assert.equal(method, 'POST'); assert.equal(url || parser.url, '/helpme'); assert.equal(versionMajor, 1); assert.equal(versionMinor, 1); @@ -438,10 +427,10 @@ function expectBody(expected) { }; var parser = newParser(REQUEST); - parser[kOnHeadersComplete] = mustCall(onHeadersComplete); - parser[kOnBody] = onBody; - parser.execute(a, 0, a.length); - parser.execute(b, 0, b.length); + parser.onHeaders = mustCall(onHeadersComplete); + parser.onBody = onBody; + parser.execute(a); + parser.execute(b); assert.equal(expected_body, ''); } @@ -482,7 +471,7 @@ function expectBody(expected) { var onHeadersComplete = function(versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) { - assert.equal(method, methods.indexOf('POST')); + assert.equal(method, 'POST'); assert.equal(url || parser.url, '/it'); assert.equal(versionMajor, 1); assert.equal(versionMinor, 1); @@ -500,11 +489,11 @@ function expectBody(expected) { }; var parser = newParser(REQUEST); - parser[kOnHeadersComplete] = mustCall(onHeadersComplete); - parser[kOnBody] = onBody; + parser.onHeaders = mustCall(onHeadersComplete); + parser.onBody = onBody; for (var i = 0; i < request.length; ++i) { - parser.execute(request, i, 1); + parser.execute(request.slice(i, i + 1)); } assert.equal(expected_body, ''); @@ -534,7 +523,7 @@ function expectBody(expected) { var onHeadersComplete1 = function(versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) { - assert.equal(method, methods.indexOf('PUT')); + assert.equal(method, 'PUT'); assert.equal(url, '/this'); assert.equal(versionMajor, 1); assert.equal(versionMinor, 1); @@ -546,7 +535,7 @@ function expectBody(expected) { var onHeadersComplete2 = function(versionMajor, versionMinor, headers, method, url, statusCode, statusMessage, upgrade, shouldKeepAlive) { - assert.equal(method, methods.indexOf('POST')); + assert.equal(method, 'POST'); assert.equal(url, '/that'); assert.equal(versionMajor, 1); assert.equal(versionMinor, 0); @@ -555,14 +544,14 @@ function expectBody(expected) { }; var parser = newParser(REQUEST); - parser[kOnHeadersComplete] = onHeadersComplete1; - parser[kOnBody] = expectBody('ping'); - parser.execute(req1, 0, req1.length); + parser.onHeaders = onHeadersComplete1; + parser.onBody = expectBody('ping'); + parser.execute(req1); parser.reinitialize(REQUEST); - parser[kOnBody] = expectBody('pong'); - parser[kOnHeadersComplete] = onHeadersComplete2; - parser.execute(req2, 0, req2.length); + parser.onBody = expectBody('pong'); + parser.onHeaders = onHeadersComplete2; + parser.execute(req2); })(); // Test parser 'this' safety @@ -574,5 +563,5 @@ assert.throws(function() { var parser = newParser(REQUEST); var notparser = { execute: parser.execute }; - notparser.execute(request, 0, request.length); + notparser.execute(request); }, TypeError); diff --git a/test/parallel/test-http-response-no-headers.js b/test/parallel/test-http-response-no-headers.js index 888eb9ac4b87e2..7b88fdc2ab5876 100644 --- a/test/parallel/test-http-response-no-headers.js +++ b/test/parallel/test-http-response-no-headers.js @@ -5,7 +5,6 @@ var http = require('http'); var net = require('net'); var expected = { - '0.9': 'I AM THE WALRUS', '1.0': 'I AM THE WALRUS', '1.1': '' }; @@ -51,8 +50,6 @@ function test(httpVersion, callback) { }); } -test('0.9', function() { - test('1.0', function() { - test('1.1'); - }); +test('1.0', function() { + test('1.1'); }); diff --git a/test/parallel/test-https-foafssl.js b/test/parallel/test-https-foafssl.js index f9b382ad830d35..11d628ff1ff598 100644 --- a/test/parallel/test-https-foafssl.js +++ b/test/parallel/test-https-foafssl.js @@ -62,7 +62,7 @@ server.listen(common.PORT, function() { server.close(); }); - client.stdin.write('GET /\n\n'); + client.stdin.write('GET /\r\n'); client.on('error', function(error) { throw error; diff --git a/test/parallel/test-process-versions.js b/test/parallel/test-process-versions.js index 637ada7fa8e349..b4fc901fd5709b 100644 --- a/test/parallel/test-process-versions.js +++ b/test/parallel/test-process-versions.js @@ -2,8 +2,7 @@ var common = require('../common'); var assert = require('assert'); -var expected_keys = ['ares', 'http_parser', 'modules', 'node', - 'uv', 'v8', 'zlib']; +var expected_keys = ['ares', 'modules', 'node', 'uv', 'v8', 'zlib']; if (common.hasCrypto) { expected_keys.push('openssl'); From 67b0e6259807d3a4c5fa32767339737adf54d9be Mon Sep 17 00:00:00 2001 From: Brian White Date: Sat, 11 Apr 2015 15:57:40 -0400 Subject: [PATCH 02/67] doc: update http documentation --- doc/api/http.markdown | 6 ------ 1 file changed, 6 deletions(-) diff --git a/doc/api/http.markdown b/doc/api/http.markdown index 5cf3b07a9cd778..8a9bb3f53031a7 100644 --- a/doc/api/http.markdown +++ b/doc/api/http.markdown @@ -43,12 +43,6 @@ list like the following: 'Host', 'mysite.com', 'accepT', '*/*' ] -## http.METHODS - -* {Array} - -A list of the HTTP methods that are supported by the parser. - ## http.STATUS_CODES * {Object} From 6b0ead30d105c5c2c01778dd1380a46a93f01789 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sat, 11 Apr 2015 16:15:46 -0400 Subject: [PATCH 03/67] http: move variable declaration --- lib/_http_parser.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 972c66d8827b4a..12beeeac87889e 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -10,6 +10,8 @@ var MAX_CHUNK_SIZE = 9007199254740992; // recommendation for status lines var MAX_HEADER_BYTES = 80 * 1024; +var RE_CHUNK_LEN = /^[0-9A-Fa-f]+/; + var RE_PCHAR = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})/; var RE_ABS_PATH = new RegExp('(?:/' + RE_PCHAR.source + '*)+'); var RE_QUERY = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*/; @@ -691,7 +693,6 @@ HTTPParser.prototype._needsEOF = function() { HTTPParser.REQUEST = 0; HTTPParser.RESPONSE = 1; -var RE_CHUNK_LEN = /^[0-9A-Fa-f]+/; function readChunkSize(str) { var m = RE_CHUNK_LEN.exec(str); if (m === null || isNaN(m = parseInt(m[0], 16)) || m > MAX_CHUNK_SIZE) From 9dd9560e85ad507516d88556d13542688665ee0f Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 12 Apr 2015 13:24:58 -0400 Subject: [PATCH 04/67] http: improve parser performance * Relax header value and status reason parsing * Avoid using RegExp for folded line detection * Use faster: string trimming, case-insensitive string comparing, CRLF searching, and case-insensitive header value searching --- lib/_http_parser.js | 135 ++++++++++++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 49 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 12beeeac87889e..b7182ca86a2c62 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -11,7 +11,20 @@ var MAX_CHUNK_SIZE = 9007199254740992; var MAX_HEADER_BYTES = 80 * 1024; var RE_CHUNK_LEN = /^[0-9A-Fa-f]+/; - +var RE_CLOSE = /close/i; +var RE_KEEPALIVE = /keep\-alive/i; +var RE_UPGRADE = /upgrade/i; +var RE_CHUNKED = /chunked/i; +var CC_CONNECTION = 'connection'.split('') + .map(function(v){return v.charCodeAt(0)}); +var CC_XFERENC = 'transfer-encoding'.split('') + .map(function(v){return v.charCodeAt(0)}); +var CC_UPGRADE = 'upgrade'.split('') + .map(function(v){return v.charCodeAt(0)}); +var CC_CONTLEN = 'content-length'.split('') + .map(function(v){return v.charCodeAt(0)}); + +// URI-parsing Regular Expressions ... var RE_PCHAR = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})/; var RE_ABS_PATH = new RegExp('(?:/' + RE_PCHAR.source + '*)+'); var RE_QUERY = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*/; @@ -109,10 +122,9 @@ request-target = origin-form | absolute-form | authority-form | ast // Note: AT LEAST a space is technically required after the status code, but // joyent/http-parser allows a CRLF immediate following the status code, so we // do also for backwards compatibility ... -var RE_STATUS_LINE = /^HTTP\/1\.([01]) ([0-9]{3})(?: ((?:[\x21-\x7E](?:[\t ]+[\x21-\x7E])*)*))?$/; +var RE_STATUS_LINE = /^HTTP\/1\.([01]) ([0-9]{3})(?: (.*))?$/; -var RE_HEADER = /^([!#$%'*+\-.^_`|~0-9A-Za-z]+):[\t ]*((?:[\x21-\x7E](?:[\t ]+[\x21-\x7E])*)*)[\t ]*$/; -var RE_FOLDED = /^[\t ]+(.*)$/; +var RE_HEADER = /^([!#$%'*+\-.^_`|~0-9A-Za-z]+):(.*)$/; var STATE_REQ_LINE = 0; var STATE_STATUS_LINE = 1; @@ -207,13 +219,13 @@ HTTPParser.prototype._processHdrLine = function(line) { } var m = RE_HEADER.exec(line); if (m === null) { - m = RE_FOLDED.exec(line); - if (m === null) { + var firstChr = line.charCodeAt(0); + if (firstChr !== 32 & firstChr !== 9) { this.execute = this._executeError; this._err = new Error('Malformed header line'); return this._err; } - var extra = m[1]; + var extra = trim(line); if (extra.length > 0) { var headers = this.headers; headers[headers.length - 1] += ' ' + extra; @@ -222,34 +234,27 @@ HTTPParser.prototype._processHdrLine = function(line) { // m[1]: field name // m[2]: field value var fieldName = m[1]; - var fieldValue = m[2]; - switch (fieldName.toLowerCase()) { - case 'connection': - var valLower = fieldValue.toLowerCase(); - if (valLower.substring(0, 5) === 'close') - this._flags |= FLAG_CONNECTION_CLOSE; - else if (valLower.substring(0, 10) === 'keep-alive') - this._flags |= FLAG_CONNECTION_KEEP_ALIVE; - else if (valLower.substring(0, 7) === 'upgrade') - this._flags |= FLAG_CONNECTION_UPGRADE; - break; - case 'transfer-encoding': - var valLower = fieldValue.toLowerCase(); - if (valLower.substring(0, 7) === 'chunked') - this._flags |= FLAG_CHUNKED; - break; - case 'upgrade': - this._flags |= FLAG_UPGRADE; - break; - case 'content-length': - var val = parseInt(fieldValue, 10); - if (isNaN(val) || val > MAX_CHUNK_SIZE) { - this.execute = this._executeError; - this._err = new Error('Bad Content-Length: ' + inspect(val)); - return this._err; - } - this._contentLen = val; - break; + var fieldValue = trim(m[2]); + if (equalsLower(fieldName, CC_CONNECTION)) { + if (fieldValue.search(RE_CLOSE) > -1) + this._flags |= FLAG_CONNECTION_CLOSE; + else if (fieldValue.search(RE_KEEPALIVE) > -1) + this._flags |= FLAG_CONNECTION_KEEP_ALIVE; + else if (fieldValue.search(RE_UPGRADE) > -1) + this._flags |= FLAG_CONNECTION_UPGRADE; + } else if (equalsLower(fieldName, CC_XFERENC)) { + if (fieldValue.search(RE_CHUNKED) > -1) + this._flags |= FLAG_CHUNKED; + } else if (equalsLower(fieldName, CC_UPGRADE)) { + this._flags |= FLAG_UPGRADE; + } else if (equalsLower(fieldName, CC_CONTLEN)) { + var val = parseInt(fieldValue, 10); + if (isNaN(val) || val > MAX_CHUNK_SIZE) { + this.execute = this._executeError; + this._err = new Error('Bad Content-Length: ' + inspect(val)); + return this._err; + } + this._contentLen = val; } var maxHeaderPairs = this.maxHeaderPairs; if (maxHeaderPairs <= 0 || ++this._nhdrpairs < maxHeaderPairs) @@ -374,12 +379,11 @@ HTTPParser.prototype._headersEnd = function() { HTTPParser.prototype._executeHeader = function(data) { var offset = 0; var len = data.length; - var idx; var seenCR = this._seenCR; var buf = this._buf; - var ret; - var bytesToAdd; var nhdrbytes = this._nhdrbytes; + var bytesToAdd; + var ret; while (offset < len) { if (seenCR) { @@ -421,10 +425,10 @@ HTTPParser.prototype._executeHeader = function(data) { } } } - var idx = data.indexOf(CRLF, offset); - if (idx > -1) { + ret = indexOfCRLF(data, len, offset); + if (ret > -1) { // Our internal buffer contains a full line - bytesToAdd = idx - offset; + bytesToAdd = ret - offset; if (bytesToAdd > 0) { nhdrbytes += bytesToAdd; if (nhdrbytes > MAX_HEADER_BYTES) { @@ -433,9 +437,9 @@ HTTPParser.prototype._executeHeader = function(data) { MAX_HEADER_BYTES + ')'); return this._err; } - buf += data.toString('binary', offset, idx); + buf += data.toString('binary', offset, ret); } - offset = idx + 2; + offset = ret + 2; ret = this._processHdrLine(buf); buf = ''; if (typeof ret === 'object') { @@ -496,7 +500,6 @@ HTTPParser.prototype._executeBodyChunked = function(data) { var seenCR = this._seenCR; var buf = this._buf; var nbytes = this._nbytes; - var idx; var ret; var bytesToAdd; @@ -511,6 +514,7 @@ HTTPParser.prototype._executeBodyChunked = function(data) { ret = readChunkSize(buf); buf = ''; if (typeof ret !== 'number') { + this.execute = this._executeError; this._err = ret; return ret; } else if (ret === 0) { @@ -543,10 +547,10 @@ HTTPParser.prototype._executeBodyChunked = function(data) { } } } - var idx = data.indexOf(CRLF, offset); - if (idx > -1) { + ret = indexOfCRLF(data, len, offset); + if (ret > -1) { // Our internal buffer contains a full line - bytesToAdd = idx - offset; + bytesToAdd = ret - offset; if (bytesToAdd > 0) { nbytes += bytesToAdd; if (nbytes > MAX_CHUNK_SIZE_LEN) { @@ -555,10 +559,10 @@ HTTPParser.prototype._executeBodyChunked = function(data) { MAX_CHUNK_SIZE_LEN + ')'); return this._err; } - buf += data.toString('ascii', offset, idx); + buf += data.toString('ascii', offset, ret); } - offset = idx + 2; + offset = ret + 2; ret = readChunkSize(buf); buf = ''; @@ -693,6 +697,20 @@ HTTPParser.prototype._needsEOF = function() { HTTPParser.REQUEST = 0; HTTPParser.RESPONSE = 1; +function indexOfCRLF(buf, buflen, offset) { + var bo1; + while (offset < buflen) { + bo1 = buf[offset + 1]; + if (buf[offset] === CR && bo1 === LF) + return offset; + else if (bo1 === CR) + ++offset; + else + offset += 2; + } + return -1; +} + function readChunkSize(str) { var m = RE_CHUNK_LEN.exec(str); if (m === null || isNaN(m = parseInt(m[0], 16)) || m > MAX_CHUNK_SIZE) @@ -700,5 +718,24 @@ function readChunkSize(str) { return m; } +function trim(value) { + var length = value.length, start, end; + for (start = 0; start < length && value.charCodeAt(start) <= 32; start++) { } + for (end = length; end > start && value.charCodeAt(end - 1) <= 32; end--) { } + return start > 0 || end < length ? value.substring(start, end) : ''; +} + +function equalsLower(input, ref) { + var inlen = input.length; + var reflen = ref.length; + if (inlen !== reflen) + return false; + for (var i = 0; i < inlen; ++i) { + if ((input.charCodeAt(i) | 0x20) !== ref[i]) + return false; + } + return true; +} + module.exports = HTTPParser; \ No newline at end of file From eb3d6792d532a9d472ce35f435e2e3268414578e Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 12 Apr 2015 13:26:59 -0400 Subject: [PATCH 05/67] benchmark: add a variety of http parser benchmarks --- benchmark/http/parser.js | 188 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 benchmark/http/parser.js diff --git a/benchmark/http/parser.js b/benchmark/http/parser.js new file mode 100644 index 00000000000000..810716777c3b51 --- /dev/null +++ b/benchmark/http/parser.js @@ -0,0 +1,188 @@ +var common = require('../common.js'); +var HTTPParser = require('_http_parser'); +var CRLF = '\r\n'; +var REQUEST = HTTPParser.REQUEST; +var RESPONSE = HTTPParser.RESPONSE; + +var bench = common.createBenchmark(main, { + type: [ + 'small-req', + 'small-res', + 'small-req-chunked', + 'small-res-chunked', + 'medium-req', + 'medium-res', + 'medium-req-chunked', + 'medium-res-chunked', + 'large-req', + 'large-res', + 'large-req-chunked', + 'large-res-chunked', + ] +}); + +var inputs = { + 'small-req': [ + 'GET /index.html HTTP/1.1' + CRLF + + 'Host: www.example.com' + CRLF + CRLF + ], + 'small-res': [ + 'HTTP/1.1 200 OK' + CRLF + + 'Date: Mon, 23 May 2005 22:38:34 GMT' + CRLF + CRLF + ], + 'small-req-chunked': [ + 'GET', + ' /index', + '.html HT', + 'TP/1.', + '1', + CRLF, + 'Host', + ': ', + 'www.example.com' + CRLF, + CRLF + ], + 'small-res-chunked': [ + 'HTTP', + '/1.', + '1 20', + '0 OK' + CRLF, + 'Date: ', + 'Mon, 23 May ', + '2005 22:38:34', + ' GMT', + CRLF, + CRLF + ], + 'medium-req': [ + 'POST /it HTTP/1.1' + CRLF + + 'Content-Type: text/plain' + CRLF + + 'Transfer-Encoding: chunked' + CRLF + + CRLF + + '3' + CRLF + + '123' + CRLF + + '6' + CRLF + + '123456' + CRLF + + 'A' + CRLF + + '1234567890' + CRLF + + '9' + CRLF + + '123456789' + CRLF + + 'C' + CRLF + + '123456789ABC' + CRLF + + 'F' + CRLF + + '123456789ABCDEF' + CRLF + + '0' + CRLF + ], + 'medium-res': [ + 'HTTP/1.0 200 OK' + CRLF + + 'Date: Mon, 23 May 2005 22:38:34 GMT' + CRLF + + 'Content-Type: text/plain' + CRLF + + 'Transfer-Encoding: chunked' + CRLF + + CRLF + + '3' + CRLF + + '123' + CRLF + + '6' + CRLF + + '123456' + CRLF + + 'A' + CRLF + + '1234567890' + CRLF + + '9' + CRLF + + '123456789' + CRLF + + 'C' + CRLF + + '123456789ABC' + CRLF + + 'F' + CRLF + + '123456789ABCDEF' + CRLF + + '0' + CRLF + ], + 'medium-req-chunked': [ + 'POST /it HTTP/', + '1.1' + CRLF, + 'Content-Type', + ': text', + '/plain', + CRLF, + 'Transfer-', + 'Encoding: chunked' + CRLF, + CRLF + '3' + CRLF + '123', + CRLF + '6' + CRLF + '123456' + CRLF + 'A' + CRLF, + '12345', + '67890' + CRLF, + '9' + CRLF + '123456789' + CRLF, + 'C' + CRLF + '123456789ABC' + CRLF + 'F' + CRLF + '123456789ABCDEF' + CRLF, + '0' + CRLF + ], + 'medium-res-chunked': [ + 'HTTP/1.0 2', + '00 OK' + CRLF + 'Date: Mo', + 'n, 23 May 2005 22', + ':38:34 GMT' + CRLF + 'Content-Type: text', + '/plain' + CRLF, + 'Transfer-Encoding: chu', + 'nked' + CRLF + CRLF + '3', + CRLF + '123' + CRLF + '6' + CRLF, + '123456' + CRLF + 'A' + CRLF + '1234567890' + CRLF, + '9' + CRLF, + '123456789' + CRLF, + 'C' + CRLF, + '123456789ABC' + CRLF, + 'F' + CRLF, + '123456789ABCDEF' + CRLF + '0' + CRLF + ], + 'large-req': [ + 'POST /foo/bar/baz?quux=42#1337 HTTP/1.0' + CRLF + + new Array(256).join('X-Filler: 42' + CRLF) + CRLF + ], + 'large-res': [ + 'HTTP/1.1 200 OK' + CRLF + + 'Content-Type: text/nonsense' + CRLF, + 'Content-Length: 3572' + CRLF + CRLF + + new Array(256).join('X-Filler: 42' + CRLF) + CRLF + ], + 'large-req-chunked': + ('POST /foo/bar/baz?quux=42#1337 HTTP/1.0' + CRLF + + new Array(256).join('X-Filler: 42' + CRLF) + CRLF).match(/.{1,144}/g) + , + 'large-res-chunked': + ('HTTP/1.1 200 OK' + CRLF + + 'Content-Type: text/nonsense' + CRLF, + 'Content-Length: 3572' + CRLF + CRLF + + new Array(256).join('X-Filler: 42' + CRLF) + CRLF).match(/.{1,144}/g) + , +}; + +function onHeadersComplete(versionMajor, versionMinor, headers, method, + url, statusCode, statusMessage, upgrade, + shouldKeepAlive) { +} +function onBody(data, start, len) { +} +function onComplete() { +} + +function main(conf) { + var chunks = inputs[conf.type]; + var n = chunks.length; + var kind = (/\-req\-?/i.exec(conf.type) ? REQUEST : RESPONSE); + + // Convert strings to Buffers first ... + for (var i = 0; i < n; ++i) + chunks[i] = new Buffer(chunks[i], 'binary'); + + for (var j = 0; j < 1000; ++j) { + var parser = new HTTPParser(kind); + parser.onHeaders = onHeadersComplete; + parser.onBody = onBody; + parser.onComplete = onComplete; + for (var i = 0; i < n; ++i) + parser.execute(chunks[i]); + } + + var parser = new HTTPParser(kind); + parser.onHeaders = onHeadersComplete; + parser.onBody = onBody; + parser.onComplete = onComplete; + + bench.start(); + for (var i = 0; i < n; ++i) + parser.execute(chunks[i]); + bench.end(n); +} From 4e37fcc31ca7166f7a9077173389884bf23e0dd2 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 12 Apr 2015 14:40:55 -0400 Subject: [PATCH 06/67] http: remove unused variable --- lib/_http_parser.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index b7182ca86a2c62..9cc0bb73ae9ca6 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -1,6 +1,5 @@ var inspect = require('util').inspect; -var CRLF = new Buffer('\r\n'); var CR = 13; var LF = 10; From fd6c1270680891d4d94ce04c021cebd38ef77f8d Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 12 Apr 2015 15:24:02 -0400 Subject: [PATCH 07/67] http: fix max chunk size string len Anything more than 14 hex characters definitely has a larger value than our max chunk size. --- lib/_http_parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 9cc0bb73ae9ca6..e68d85d0d488b3 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -3,7 +3,7 @@ var inspect = require('util').inspect; var CR = 13; var LF = 10; -var MAX_CHUNK_SIZE_LEN = 16; // max length of chunk size line +var MAX_CHUNK_SIZE_LEN = 14; // max length of chunk size line var MAX_CHUNK_SIZE = 9007199254740992; // RFC 7230 recommends at least 8000 max bytes for request line, but no // recommendation for status lines From 6a1e1575d1f57a145c28ea236434ce4c1ea7825b Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 12 Apr 2015 15:24:39 -0400 Subject: [PATCH 08/67] http: use faster NaN check --- lib/_http_parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index e68d85d0d488b3..ca86161adbc4cf 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -248,7 +248,7 @@ HTTPParser.prototype._processHdrLine = function(line) { this._flags |= FLAG_UPGRADE; } else if (equalsLower(fieldName, CC_CONTLEN)) { var val = parseInt(fieldValue, 10); - if (isNaN(val) || val > MAX_CHUNK_SIZE) { + if (val !== val || val > MAX_CHUNK_SIZE) { this.execute = this._executeError; this._err = new Error('Bad Content-Length: ' + inspect(val)); return this._err; From 2e6348384bb60351a1d89af79509f53fecc8edab Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 12 Apr 2015 15:25:15 -0400 Subject: [PATCH 09/67] http: use faster hex number decoding --- lib/_http_parser.js | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index ca86161adbc4cf..1f05737b20cdff 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -5,6 +5,30 @@ var LF = 10; var MAX_CHUNK_SIZE_LEN = 14; // max length of chunk size line var MAX_CHUNK_SIZE = 9007199254740992; +var UNHEX = { + 48: 0, + 49: 1, + 50: 2, + 51: 3, + 52: 4, + 53: 5, + 54: 6, + 55: 7, + 56: 8, + 57: 9, + 65: 10, + 66: 11, + 67: 12, + 68: 13, + 69: 14, + 70: 15, + 97: 10, + 98: 11, + 99: 12, + 100: 13, + 101: 14, + 102: 15 +}; // RFC 7230 recommends at least 8000 max bytes for request line, but no // recommendation for status lines var MAX_HEADER_BYTES = 80 * 1024; @@ -711,10 +735,23 @@ function indexOfCRLF(buf, buflen, offset) { } function readChunkSize(str) { - var m = RE_CHUNK_LEN.exec(str); - if (m === null || isNaN(m = parseInt(m[0], 16)) || m > MAX_CHUNK_SIZE) - m = new Error('Invalid chunk size: ' + inspect(str)); - return m; + var val, dec; + for (var i = 0; i < str.length; ++i) { + dec = UNHEX[str.charCodeAt(i)]; + if (dec === undefined) + break; + else if (val === undefined) + val = dec; + else { + val *= 16; + val += dec; + } + } + if (val === undefined) + return new Error('Invalid chunk size'); + if (val > MAX_CHUNK_SIZE) + return new Error('Chunk size too big'); + return val; } function trim(value) { From 1ead6489240625da3a3646674819b4bc4bb29ddd Mon Sep 17 00:00:00 2001 From: Brian White Date: Mon, 13 Apr 2015 00:01:49 -0400 Subject: [PATCH 10/67] benchmark: fix V8 warmup for http parser benchmarks I think it's more accurate to "warm up" the one parser instance instead of creating many new throw-away instances since instances are typically reused in core. --- benchmark/http/parser.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/benchmark/http/parser.js b/benchmark/http/parser.js index 810716777c3b51..4fc2167df3d7d2 100644 --- a/benchmark/http/parser.js +++ b/benchmark/http/parser.js @@ -167,20 +167,17 @@ function main(conf) { for (var i = 0; i < n; ++i) chunks[i] = new Buffer(chunks[i], 'binary'); - for (var j = 0; j < 1000; ++j) { - var parser = new HTTPParser(kind); - parser.onHeaders = onHeadersComplete; - parser.onBody = onBody; - parser.onComplete = onComplete; - for (var i = 0; i < n; ++i) - parser.execute(chunks[i]); - } - var parser = new HTTPParser(kind); parser.onHeaders = onHeadersComplete; parser.onBody = onBody; parser.onComplete = onComplete; + // Allow V8 to optimize first ... + for (var j = 0; j < 1000; ++j) { + for (var i = 0; i < n; ++i) + parser.execute(chunks[i]); + } + bench.start(); for (var i = 0; i < n; ++i) parser.execute(chunks[i]); From b2547533cee28b3ebe2574f7e42b96631f374e74 Mon Sep 17 00:00:00 2001 From: Brian White Date: Mon, 13 Apr 2015 02:02:35 -0400 Subject: [PATCH 11/67] http: fix comment typo --- lib/_http_parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 1f05737b20cdff..bf2aa894dc4cac 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -143,7 +143,7 @@ request-target = origin-form | absolute-form | authority-form | ast */ // Note: AT LEAST a space is technically required after the status code, but -// joyent/http-parser allows a CRLF immediate following the status code, so we +// joyent/http-parser allows a CRLF immediately following the status code, so we // do also for backwards compatibility ... var RE_STATUS_LINE = /^HTTP\/1\.([01]) ([0-9]{3})(?: (.*))?$/; From 2c4ebc2e42c86eb583d5e2a488ac159208a65ef8 Mon Sep 17 00:00:00 2001 From: Brian White Date: Tue, 14 Apr 2015 13:08:42 -0400 Subject: [PATCH 12/67] benchmark: improve http parser benchmark more This increases the loop count for the timed region and removes a couple of unlikely inputs. --- benchmark/http/parser.js | 42 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/benchmark/http/parser.js b/benchmark/http/parser.js index 4fc2167df3d7d2..5795844ee707fe 100644 --- a/benchmark/http/parser.js +++ b/benchmark/http/parser.js @@ -5,11 +5,10 @@ var REQUEST = HTTPParser.REQUEST; var RESPONSE = HTTPParser.RESPONSE; var bench = common.createBenchmark(main, { + n: [100000], type: [ 'small-req', 'small-res', - 'small-req-chunked', - 'small-res-chunked', 'medium-req', 'medium-res', 'medium-req-chunked', @@ -30,30 +29,6 @@ var inputs = { 'HTTP/1.1 200 OK' + CRLF + 'Date: Mon, 23 May 2005 22:38:34 GMT' + CRLF + CRLF ], - 'small-req-chunked': [ - 'GET', - ' /index', - '.html HT', - 'TP/1.', - '1', - CRLF, - 'Host', - ': ', - 'www.example.com' + CRLF, - CRLF - ], - 'small-res-chunked': [ - 'HTTP', - '/1.', - '1 20', - '0 OK' + CRLF, - 'Date: ', - 'Mon, 23 May ', - '2005 22:38:34', - ' GMT', - CRLF, - CRLF - ], 'medium-req': [ 'POST /it HTTP/1.1' + CRLF + 'Content-Type: text/plain' + CRLF + @@ -160,11 +135,12 @@ function onComplete() { function main(conf) { var chunks = inputs[conf.type]; - var n = chunks.length; + var n = +conf.n; + var nchunks = chunks.length; var kind = (/\-req\-?/i.exec(conf.type) ? REQUEST : RESPONSE); // Convert strings to Buffers first ... - for (var i = 0; i < n; ++i) + for (var i = 0; i < nchunks; ++i) chunks[i] = new Buffer(chunks[i], 'binary'); var parser = new HTTPParser(kind); @@ -174,12 +150,14 @@ function main(conf) { // Allow V8 to optimize first ... for (var j = 0; j < 1000; ++j) { - for (var i = 0; i < n; ++i) + for (var i = 0; i < nchunks; ++i) parser.execute(chunks[i]); } bench.start(); - for (var i = 0; i < n; ++i) - parser.execute(chunks[i]); - bench.end(n); + for (var c = 0; c < n; ++c) { + for (var i = 0; i < nchunks; ++i) + parser.execute(chunks[i]); + } + bench.end(n * nchunks); } From f5ba8cfd080a75172da186bc1eed0b955b31e20c Mon Sep 17 00:00:00 2001 From: Brian White Date: Tue, 14 Apr 2015 13:09:00 -0400 Subject: [PATCH 13/67] http: style change --- lib/_http_parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index bf2aa894dc4cac..18dcfb1d2e4c74 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -299,9 +299,9 @@ HTTPParser.prototype._processHdrLine = function(line) { // m[2]: request target // m[3]: HTTP minor version var method = m[1]; + var minor = m[3]; this.method = method; this.url = m[2]; - var minor = m[3]; if (minor === undefined) { // HTTP/0.9 ugh... if (method !== 'GET') { From f4d786e553020d1d70d0789d47ec85df3fe4c3cf Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 20:38:33 -0400 Subject: [PATCH 14/67] http: remove chunk size string length checking This was initially designed to verify we didn't have more digits than JavaScript can handle, but it's erroneously reporting errors when there are chunk extensions on the same line. The check's usefulness is semi-questionable anyway, so let's remove it for now. --- lib/_http_parser.js | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 18dcfb1d2e4c74..de7ef7d347a8d3 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -3,7 +3,6 @@ var inspect = require('util').inspect; var CR = 13; var LF = 10; -var MAX_CHUNK_SIZE_LEN = 14; // max length of chunk size line var MAX_CHUNK_SIZE = 9007199254740992; var UNHEX = { 48: 0, @@ -560,30 +559,14 @@ HTTPParser.prototype._executeBodyChunked = function(data) { } else { // False match buf += '\r'; - ++nbytes; - - if (nbytes > MAX_CHUNK_SIZE_LEN) { - this.execute = this._executeError; - this._err = new Error('Chunk size lint limit exceeded (' + - MAX_CHUNK_SIZE_LEN + ')'); - return this._err; - } } } ret = indexOfCRLF(data, len, offset); if (ret > -1) { // Our internal buffer contains a full line bytesToAdd = ret - offset; - if (bytesToAdd > 0) { - nbytes += bytesToAdd; - if (nbytes > MAX_CHUNK_SIZE_LEN) { - this.execute = this._executeError; - this._err = new Error('Chunk size lint limit exceeded (' + - MAX_CHUNK_SIZE_LEN + ')'); - return this._err; - } + if (bytesToAdd > 0) buf += data.toString('ascii', offset, ret); - } offset = ret + 2; ret = readChunkSize(buf); @@ -616,16 +599,6 @@ HTTPParser.prototype._executeBodyChunked = function(data) { seenCR = true; end = len - 1; } - - bytesToAdd = end - offset; - nbytes += bytesToAdd; - - if (nbytes > MAX_CHUNK_SIZE_LEN) { - this.execute = this._executeError; - this._err = new Error('Chunk size lint limit exceeded (' + - MAX_CHUNK_SIZE_LEN + ')'); - return this._err; - } buf += data.toString('ascii', offset, end); offset = len; // break out of while loop } From 455ea89d8a9b944fc4a00038f17c127c4ca95494 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 20:38:47 -0400 Subject: [PATCH 15/67] http: remove unused variable --- lib/_http_parser.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index de7ef7d347a8d3..484b42f6b2bf72 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -32,7 +32,6 @@ var UNHEX = { // recommendation for status lines var MAX_HEADER_BYTES = 80 * 1024; -var RE_CHUNK_LEN = /^[0-9A-Fa-f]+/; var RE_CLOSE = /close/i; var RE_KEEPALIVE = /keep\-alive/i; var RE_UPGRADE = /upgrade/i; From 237e8d5504a6d4e688df27ab212c51f185066752 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 20:41:15 -0400 Subject: [PATCH 16/67] http: allow double quotes and non-ASCII bytes in request URI As the comments explain, these are not technically allowed, but since joyent/http-parser previously allowed them, we support them for better backwards compatibility. --- lib/_http_parser.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 484b42f6b2bf72..796acf2a095ff2 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -46,9 +46,22 @@ var CC_CONTLEN = 'content-length'.split('') .map(function(v){return v.charCodeAt(0)}); // URI-parsing Regular Expressions ... -var RE_PCHAR = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})/; + +// Note: double quotes are not allowed anywhere in request URIs, but +// joyent/http-parser allowed it previously so we do too for better backwards +// compatibility ... +// Note: non-ASCII characters are not allowed anywhere in request URIs, but +// joyent/http-parser allowed it previously so we do too for better backwards +// compatibility ... +var RE_PCHAR = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:@"\x80-\xFF]|%[0-9A-Fa-f]{2})/; var RE_ABS_PATH = new RegExp('(?:/' + RE_PCHAR.source + '*)+'); -var RE_QUERY = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*/; +// Note: double quotes are not allowed anywhere in request URIs, but +// joyent/http-parser allowed it previously so we do too for better backwards +// compatibility ... +// Note: non-ASCII characters are not allowed anywhere in request URIs, but +// joyent/http-parser allowed it previously so we do too for better backwards +// compatibility ... +var RE_QUERY = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:@/?"\x80-\xFF]|%[0-9A-Fa-f]{2})*/; // Note: fragments are technically not allowed in the request line, but // joyent/http-parser allowed it previously so we do also for backwards // compatibility ... From 48e1dc76298976834079c32a8ff9ea45b02e290f Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 20:41:49 -0400 Subject: [PATCH 17/67] http: improve comment wording --- lib/_http_parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 796acf2a095ff2..4d2dbb6552279b 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -63,7 +63,7 @@ var RE_ABS_PATH = new RegExp('(?:/' + RE_PCHAR.source + '*)+'); // compatibility ... var RE_QUERY = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:@/?"\x80-\xFF]|%[0-9A-Fa-f]{2})*/; // Note: fragments are technically not allowed in the request line, but -// joyent/http-parser allowed it previously so we do also for backwards +// joyent/http-parser allowed it previously so we do too for better backwards // compatibility ... var RE_ORIGIN_FORM = new RegExp('(?:' + RE_ABS_PATH.source + '(?:\\?' + RE_QUERY.source + ')?(?:#' + RE_QUERY.source + ')?)'); From ade4029295ad4369e4e0f7c0baf07eb85b4eb168 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 20:42:36 -0400 Subject: [PATCH 18/67] http: improve comment wording (again) --- lib/_http_parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 4d2dbb6552279b..53d2188a0c9ca1 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -100,7 +100,7 @@ var RE_PATH_ROOTLESS = new RegExp('(?:' + RE_PCHAR.source + '+' + RE_PATH_ABEMPT var RE_PATH_ABSOLUTE = new RegExp('(?:/' + RE_PATH_ROOTLESS.source + '?)'); var RE_HIER_PART = new RegExp('//(?:' + RE_AUTHORITY.source + ')(?:' + RE_PATH_ABEMPTY + '|' + RE_PATH_ABSOLUTE + '|' + RE_PATH_ROOTLESS + '|)'); // Note: fragments are technically not allowed in the request line, but -// joyent/http-parser allowed it previously so we do also for backwards +// joyent/http-parser allowed it previously so we do too for better backwards // compatibility ... var RE_ABSOLUTE_FORM = new RegExp('(?:' + RE_SCHEME.source + ':' + RE_HIER_PART.source + '(?:\\?' + RE_QUERY.source + ')?(?:#' + RE_QUERY.source + ')?)'); From d3b4976d251eb8190e3e5d659f7d2fbfb1e1916d Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 20:43:47 -0400 Subject: [PATCH 19/67] http: fix parsing of authority portion of request URI --- lib/_http_parser.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 53d2188a0c9ca1..babd57c20c516d 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -93,8 +93,10 @@ var RE_IPV6 = new RegExp('(?:' + '|(?:(?:' + RE_H16_COLON.source + '{0,6}' + RE_H16.source + ')?::)'); var RE_REGNAME = /(?:[A-Za-z0-9\-._~!$&'()*+,;=]|%[0-9A-Fa-f]{2})*/; -var RE_HOST = new RegExp('(?:(?:\\[' + RE_IPV6.source + '\\])|(?:' + RE_IPV4.source + ')|' + RE_REGNAME.source + ')'); -var RE_AUTHORITY = new RegExp('(?:(?:' + RE_USERINFO.source + ')?' + RE_HOST.source + '(?::[0-9]*)?)'); +var RE_HOST = new RegExp('(?:(?:\\[' + RE_IPV6.source + '\\])|(?:' + + RE_IPV4.source + ')|' + RE_REGNAME.source + ')'); +var RE_AUTHORITY = new RegExp('(?:(?:' + RE_USERINFO.source + '@)?' + + RE_HOST.source + '(?::[0-9]*)?)'); var RE_PATH_ABEMPTY = new RegExp('(?:/' + RE_PCHAR.source + '*)*'); var RE_PATH_ROOTLESS = new RegExp('(?:' + RE_PCHAR.source + '+' + RE_PATH_ABEMPTY.source + ')'); var RE_PATH_ABSOLUTE = new RegExp('(?:/' + RE_PATH_ROOTLESS.source + '?)'); From 6ee84a8967d98be981a90855ef539c313d4c163c Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 20:44:47 -0400 Subject: [PATCH 20/67] http: fix parsing hier-part of request URI --- lib/_http_parser.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index babd57c20c516d..8d5af4964394ac 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -100,7 +100,10 @@ var RE_AUTHORITY = new RegExp('(?:(?:' + RE_USERINFO.source + '@)?' + var RE_PATH_ABEMPTY = new RegExp('(?:/' + RE_PCHAR.source + '*)*'); var RE_PATH_ROOTLESS = new RegExp('(?:' + RE_PCHAR.source + '+' + RE_PATH_ABEMPTY.source + ')'); var RE_PATH_ABSOLUTE = new RegExp('(?:/' + RE_PATH_ROOTLESS.source + '?)'); -var RE_HIER_PART = new RegExp('//(?:' + RE_AUTHORITY.source + ')(?:' + RE_PATH_ABEMPTY + '|' + RE_PATH_ABSOLUTE + '|' + RE_PATH_ROOTLESS + '|)'); +var RE_HIER_PART = new RegExp('(?:(?://' + RE_AUTHORITY.source + + RE_PATH_ABEMPTY.source + ')|' + + RE_PATH_ABSOLUTE.source + '|' + + RE_PATH_ROOTLESS.source + '|)'); // Note: fragments are technically not allowed in the request line, but // joyent/http-parser allowed it previously so we do too for better backwards // compatibility ... From 766c7313be5f8c8b10c015b788498da8d8a5ffdf Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 20:48:47 -0400 Subject: [PATCH 21/67] http: fix whitespace preservation if folding is encountered Addtitionally this re-checks special headers in case the values we check for were on folded lines. --- lib/_http_parser.js | 72 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 8 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 8d5af4964394ac..acac535d5f9aa0 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -221,6 +221,7 @@ HTTPParser.prototype.reinitialize = function(type) { this._nbytes = 0; this._nhdrbytes = 0; this._nhdrpairs = 0; + this._folded = false; this._buf = ''; this._seenCR = false; @@ -256,6 +257,10 @@ HTTPParser.prototype._processHdrLine = function(line) { this._headersEnd(); return; } + var headers = this.headers; + var headerslen = headers.length; + var fieldName; + var fieldValue; var m = RE_HEADER.exec(line); if (m === null) { var firstChr = line.charCodeAt(0); @@ -264,16 +269,58 @@ HTTPParser.prototype._processHdrLine = function(line) { this._err = new Error('Malformed header line'); return this._err; } - var extra = trim(line); + // RFC 7230 compliant, but less backwards compatible: + var extra = ltrim(line); if (extra.length > 0) { - var headers = this.headers; - headers[headers.length - 1] += ' ' + extra; + if (headerslen === 0) { + this.execute = this._executeError; + this._err = new Error('Malformed header line'); + return this._err; + } + this._folded = true; + fieldName = headers[headerslen - 2]; + fieldValue = headers[headerslen - 1] + ' ' + extra; + // Need to re-check value since matched values may now exist ... + if (equalsLower(fieldName, CC_CONNECTION)) { + if (fieldValue.search(RE_CLOSE) > -1) + this._flags |= FLAG_CONNECTION_CLOSE; + if (fieldValue.search(RE_KEEPALIVE) > -1) + this._flags |= FLAG_CONNECTION_KEEP_ALIVE; + if (fieldValue.search(RE_UPGRADE) > -1) + this._flags |= FLAG_CONNECTION_UPGRADE; + } else if (equalsLower(fieldName, CC_XFERENC)) { + if (fieldValue.search(RE_CHUNKED) > -1) + this._flags |= FLAG_CHUNKED; + } else if (equalsLower(fieldName, CC_UPGRADE)) { + this._flags |= FLAG_UPGRADE; + } else if (equalsLower(fieldName, CC_CONTLEN)) { + var val = parseInt(fieldValue, 10); + if (val !== val || val > MAX_CHUNK_SIZE) { + this.execute = this._executeError; + this._err = new Error('Bad Content-Length: ' + inspect(val)); + return this._err; + } + this._contentLen = val; + } + headers[headerslen - 1] = fieldValue; } } else { + // Ensures that trailing whitespace after the last folded line for + // header values gets trimmed + if (this._folded) { + this._folded = false; + if (headerslen === 0) { + this.execute = this._executeError; + this._err = new Error('Malformed header line'); + return this._err; + } + headers[headerslen - 1] = trim(headers[headerslen - 1]); + } else if (headerslen > 0) + headers[headerslen - 1] = trim(headers[headerslen - 1]); // m[1]: field name // m[2]: field value - var fieldName = m[1]; - var fieldValue = trim(m[2]); + fieldName = m[1]; + fieldValue = m[2]; if (equalsLower(fieldName, CC_CONNECTION)) { if (fieldValue.search(RE_CLOSE) > -1) this._flags |= FLAG_CONNECTION_CLOSE; @@ -297,7 +344,7 @@ HTTPParser.prototype._processHdrLine = function(line) { } var maxHeaderPairs = this.maxHeaderPairs; if (maxHeaderPairs <= 0 || ++this._nhdrpairs < maxHeaderPairs) - this.headers.push(fieldName, fieldValue); + headers.push(fieldName, fieldValue); } break; case STATE_REQ_LINE: @@ -387,15 +434,24 @@ HTTPParser.prototype._headersEnd = function() { this.execute = this._executeBodyEOF; } + var headers = this.headers; + var headerslen = headers.length; if ((flags & FLAG_TRAILING) > 0) { + if (this._folded) + this._folded = false; + if (headerslen > 0) + headers[headerslen - 1] = trim(headers[headerslen - 1]); this.onComplete && this.onComplete(); this.reinitialize(this.type); return; } else { - var headers = this.headers; this.headers = []; if (this.onHeaders) { - ret = this.onHeaders(this.httpMajor, this.httpMinor, headers, this.method, + if (this._folded) + this._folded = false; + if (headerslen > 0) + headers[headerslen - 1] = trim(headers[headerslen - 1]); + ret = this.onHeaders(httpMajor, httpMinor, headers, method, this.url, this.statusCode, this.statusText, upgrade, keepalive); if (ret === true) From 234864a2c1c3495e9705a6b25945a6e2cb3faa2b Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 20:50:08 -0400 Subject: [PATCH 22/67] http: add extra internal state check in finish() --- lib/_http_parser.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index acac535d5f9aa0..6764666fd9d81c 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -240,10 +240,18 @@ HTTPParser.prototype.reinitialize = function(type) { this.statusText = undefined; }; HTTPParser.prototype.finish = function() { - if (this._state === STATE_BODY_EOF) { + var state = this._state; + if (state === STATE_BODY_EOF) { this.execute = this._executeBodyIgnore; this._state = STATE_COMPLETE; this.onComplete && this.onComplete(); + } else if (this.execute !== this._executeError && + state !== STATE_REQ_LINE && + state !== STATE_STATUS_LINE && + state !== STATE_COMPLETE) { + this.execute = this._executeError; + this._err = new Error('Invalid EOF state'); + return this._err; } }; HTTPParser.prototype.close = function() {}; From b59f4f1998e5319479aed4002b3f4eef94434f1c Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 20:52:44 -0400 Subject: [PATCH 23/67] http: fix keepalive check This should now directly reflect how joyent/http-parser does keepalive checking. --- lib/_http_parser.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 6764666fd9d81c..7a30fac08a7b1f 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -415,22 +415,20 @@ HTTPParser.prototype._processHdrLine = function(line) { }; HTTPParser.prototype._headersEnd = function() { var flags = this._flags; - var methodLower = this.method && this.method.toLowerCase(); + var type = this.type; + var method = this.method; var upgrade = ((flags & FLAG_ANY_UPGRADE) === FLAG_ANY_UPGRADE || - methodLower === 'connect'); - var keepalive = ((flags & FLAG_CONNECTION_CLOSE) === 0); + (type === REQUEST && equalsLower(method, CC_CONNECT))); var contentLen = this._contentLen; + var httpMajor = this.httpMajor; + var httpMinor = this.httpMinor; + var keepalive = this._shouldKeepAlive(httpMajor, httpMinor, flags); var ret; this._buf = ''; this._seenCR = false; this._nbytes = 0; - if ((this.httpMajor === 0 && this.httpMinor === 9) || - (this.httpMinor === 0 && (flags & FLAG_CONNECTION_KEEP_ALIVE) === 0)) { - keepalive = false; - } - if ((flags & FLAG_CHUNKED) > 0) { this._state = STATE_BODY_CHUNKED_SIZE; this.execute = this._executeBodyChunked; @@ -450,7 +448,7 @@ HTTPParser.prototype._headersEnd = function() { if (headerslen > 0) headers[headerslen - 1] = trim(headers[headerslen - 1]); this.onComplete && this.onComplete(); - this.reinitialize(this.type); + this.reinitialize(type); return; } else { this.headers = []; @@ -476,7 +474,7 @@ HTTPParser.prototype._headersEnd = function() { contentLen === undefined && !this._needsEOF())) { this.onComplete && this.onComplete(); - this.reinitialize(this.type); + this.reinitialize(type); } }; HTTPParser.prototype._executeHeader = function(data) { @@ -747,6 +745,16 @@ HTTPParser.prototype._executeError = function(data) { return this._err; }; HTTPParser.prototype.execute = HTTPParser.prototype._executeHeader; +HTTPParser.prototype._shouldKeepAlive = function(httpMajor, httpMinor, flags) { + if (httpMajor > 0 && httpMinor > 0) { + if ((flags & FLAG_CONNECTION_CLOSE) > 0) + return false; + } else { + if ((flags & FLAG_CONNECTION_KEEP_ALIVE) === 0) + return false; + } + return !this._needsEOF(); +}; HTTPParser.prototype._needsEOF = function() { if (this.type === HTTPParser.REQUEST) return false; From a332c745dbf3beba465cca17ccccc043eeec3688 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 20:53:27 -0400 Subject: [PATCH 24/67] http: check for multiple values in Connection header value --- lib/_http_parser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 7a30fac08a7b1f..2baad895bbe822 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -332,9 +332,9 @@ HTTPParser.prototype._processHdrLine = function(line) { if (equalsLower(fieldName, CC_CONNECTION)) { if (fieldValue.search(RE_CLOSE) > -1) this._flags |= FLAG_CONNECTION_CLOSE; - else if (fieldValue.search(RE_KEEPALIVE) > -1) + if (fieldValue.search(RE_KEEPALIVE) > -1) this._flags |= FLAG_CONNECTION_KEEP_ALIVE; - else if (fieldValue.search(RE_UPGRADE) > -1) + if (fieldValue.search(RE_UPGRADE) > -1) this._flags |= FLAG_CONNECTION_UPGRADE; } else if (equalsLower(fieldName, CC_XFERENC)) { if (fieldValue.search(RE_CHUNKED) > -1) From 9a7f8d507bd3f2ee4b8a15a83376ac397e330ac5 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 20:56:52 -0400 Subject: [PATCH 25/67] http: ensure first byte is printable ASCII This helps detect bad input early to prevent a "hung" state where the parser keeps looking for a CRLF. Generally this shouldn't be an issue, but could happen if TLS handshaking data is mistakenly written to the parser for example. --- lib/_http_parser.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 2baad895bbe822..c879879380700b 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -208,7 +208,7 @@ function HTTPParser(type) { this.reinitialize(type); } HTTPParser.prototype.reinitialize = function(type) { - this.execute = this._executeHeader; + this.execute = this._executeStartLine; this.type = type; if (type === HTTPParser.REQUEST) this._state = STATE_REQ_LINE; @@ -477,6 +477,18 @@ HTTPParser.prototype._headersEnd = function() { this.reinitialize(type); } }; +HTTPParser.prototype._executeStartLine = function(data) { + if (data.length === 0) + return 0; + var firstByte = data[0]; + if ((firstByte < 32 || firstByte >= 127) && firstByte !== CR) { + this.execute = this._executeError; + this._err = new Error('Invalid byte(s) in start line'); + return this._err; + } + this.execute = this._executeHeader; + return this.execute(data); +}; HTTPParser.prototype._executeHeader = function(data) { var offset = 0; var len = data.length; @@ -744,7 +756,7 @@ HTTPParser.prototype._executeBodyIgnore = function(data) { HTTPParser.prototype._executeError = function(data) { return this._err; }; -HTTPParser.prototype.execute = HTTPParser.prototype._executeHeader; +HTTPParser.prototype.execute = HTTPParser.prototype._executeStartLine; HTTPParser.prototype._shouldKeepAlive = function(httpMajor, httpMinor, flags) { if (httpMajor > 0 && httpMinor > 0) { if ((flags & FLAG_CONNECTION_CLOSE) > 0) From ac584b9b5c7d0e131590d408673f277aecb48d29 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 20:58:00 -0400 Subject: [PATCH 26/67] http: return early if data chunk is empty --- lib/_http_parser.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index c879879380700b..acd84469701511 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -490,8 +490,12 @@ HTTPParser.prototype._executeStartLine = function(data) { return this.execute(data); }; HTTPParser.prototype._executeHeader = function(data) { - var offset = 0; var len = data.length; + + if (len === 0) + return 0; + + var offset = 0; var seenCR = this._seenCR; var buf = this._buf; var nhdrbytes = this._nhdrbytes; @@ -607,7 +611,7 @@ HTTPParser.prototype._executeBodyChunked = function(data) { var len = data.length; if (len === 0) - return; + return 0; var offset = 0; var seenCR = this._seenCR; @@ -733,6 +737,10 @@ HTTPParser.prototype._executeBodyChunked = function(data) { }; HTTPParser.prototype._executeBodyLiteral = function(data) { var len = data.length; + + if (len === 0) + return 0; + var nbytes = this._contentLen; if (len >= nbytes) { this.reinitialize(this.type); @@ -747,6 +755,10 @@ HTTPParser.prototype._executeBodyLiteral = function(data) { }; HTTPParser.prototype._executeBodyEOF = function(data) { var len = data.length; + + if (len === 0) + return 0; + this.onBody(data, 0, len); return len; }; From 382b12cc9f24fcfb4a69cea890cbcd571f9d3c97 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 20:59:59 -0400 Subject: [PATCH 27/67] http: remove duplicate code Typically the functions that return Error objects already have set the internal error state, so there is no need to do it twice. --- lib/_http_parser.js | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index acd84469701511..cca70df8e41ebb 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -510,20 +510,17 @@ HTTPParser.prototype._executeHeader = function(data) { ++offset; ret = this._processHdrLine(buf); buf = ''; - if (typeof ret === 'object') { - this._err = ret; + if (typeof ret === 'object') return ret; - } else if (ret === undefined) { + else if (ret === undefined) { var state = this._state; if (state !== STATE_HEADER) { // Begin of body or end of message if (state < STATE_COMPLETE && offset < len) { // Execute extra body bytes ret = this.execute(data.slice(offset)); - if (typeof ret !== 'number') { - this._err = ret; + if (typeof ret !== 'number') return ret; - } return offset + ret; } else if (state === STATE_COMPLETE) this.reinitialize(this.type); @@ -559,20 +556,17 @@ HTTPParser.prototype._executeHeader = function(data) { offset = ret + 2; ret = this._processHdrLine(buf); buf = ''; - if (typeof ret === 'object') { - this._err = ret; + if (typeof ret === 'object') return ret; - } else if (ret === undefined) { + else if (ret === undefined) { var state = this._state; if (state !== STATE_HEADER) { // Begin of body or end of message if (state < STATE_COMPLETE && offset < len) { // Execute extra body bytes ret = this.execute(data.slice(offset)); - if (typeof ret !== 'number') { - this._err = ret; + if (typeof ret !== 'number') return ret; - } return offset + ret; } else if (state === STATE_COMPLETE) this.reinitialize(this.type); @@ -640,12 +634,13 @@ HTTPParser.prototype._executeBodyChunked = function(data) { this._flags |= FLAG_TRAILING; this._state = STATE_HEADER; this.execute = this._executeHeader; - ret = this.execute(data.slice(offset)); - if (typeof ret !== 'number') { - this._err = ret; - return ret; + if (offset < len) { + ret = this.execute(data.slice(offset)); + if (typeof ret !== 'number') + return ret; + return offset + ret; } - return offset + ret; + return offset; } else { nbytes = ret; this._state = STATE_BODY_CHUNKED_BYTES; @@ -676,12 +671,13 @@ HTTPParser.prototype._executeBodyChunked = function(data) { this._flags |= FLAG_TRAILING; this._state = STATE_HEADER; this.execute = this._executeHeader; - ret = this.execute(data.slice(offset)); - if (typeof ret !== 'number') { - this._err = ret; - return ret; + if (offset < len) { + ret = this.execute(data.slice(offset)); + if (typeof ret !== 'number') + return ret; + return offset + ret; } - return offset + ret; + return offset; } else { nbytes = ret; this._state = STATE_BODY_CHUNKED_BYTES; From 8d5d474fc8b5d8f13938d25c2830b51b8bbb2f64 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 21:00:30 -0400 Subject: [PATCH 28/67] http: add missing execute handler change --- lib/_http_parser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index cca70df8e41ebb..472f5bbaba549d 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -663,6 +663,7 @@ HTTPParser.prototype._executeBodyChunked = function(data) { buf = ''; if (typeof ret !== 'number') { + this.execute = this._executeError; this._err = ret; return ret; } else if (ret === 0) { From a5194be52ae6ebe3ea9912a9cf0928fef58a88a3 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 21:00:57 -0400 Subject: [PATCH 29/67] http: fix typo --- lib/_http_parser.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 472f5bbaba549d..168a4a68cfeaa9 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -784,10 +784,10 @@ HTTPParser.prototype._needsEOF = function() { var status = this.statusCode; var flags = this._flags; if ((status !== undefined && - (status === 204 || // No Content - status === 304 || // Not Modified - parseInt(status / 100, 1) === 1)) || // 1xx e.g. Continue - flags & FLAG_SKIPBODY) { // response to a HEAD request + (status === 204 || // No Content + status === 304 || // Not Modified + parseInt(status / 100, 10) === 1)) || // 1xx e.g. Continue + (flags & FLAG_SKIPBODY) > 0) { // response to a HEAD request return false; } From 434e1bbd6195e4c1f581d3009c9fca8219c8594a Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 21:01:36 -0400 Subject: [PATCH 30/67] http: add missing ltrim function --- lib/_http_parser.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 168a4a68cfeaa9..6748b3d882b003 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -837,6 +837,14 @@ function readChunkSize(str) { return val; } +function ltrim(value) { + var length = value.length, start; + for (start = 0; + start < length && + (value.charCodeAt(start) === 32 || value.charCodeAt(start) === 9); + ++start); + return start > 0 ? value.slice(start) : value; +} function trim(value) { var length = value.length, start, end; for (start = 0; start < length && value.charCodeAt(start) <= 32; start++) { } From 6e0c6ba6d068fad3d4ee48cf4c7bcf37a3073ff9 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 21:02:26 -0400 Subject: [PATCH 31/67] http: lint and style changes --- lib/_http_parser.js | 143 +++++++++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 60 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 6748b3d882b003..62467f477703ce 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -24,9 +24,9 @@ var UNHEX = { 97: 10, 98: 11, 99: 12, - 100: 13, - 101: 14, - 102: 15 + 100: 13, + 101: 14, + 102: 15 }; // RFC 7230 recommends at least 8000 max bytes for request line, but no // recommendation for status lines @@ -36,14 +36,11 @@ var RE_CLOSE = /close/i; var RE_KEEPALIVE = /keep\-alive/i; var RE_UPGRADE = /upgrade/i; var RE_CHUNKED = /chunked/i; -var CC_CONNECTION = 'connection'.split('') - .map(function(v){return v.charCodeAt(0)}); -var CC_XFERENC = 'transfer-encoding'.split('') - .map(function(v){return v.charCodeAt(0)}); -var CC_UPGRADE = 'upgrade'.split('') - .map(function(v){return v.charCodeAt(0)}); -var CC_CONTLEN = 'content-length'.split('') - .map(function(v){return v.charCodeAt(0)}); +var CC_CONNECT = 'connect'.split('').map(getFirstCharCode); +var CC_CONNECTION = 'connection'.split('').map(getFirstCharCode); +var CC_XFERENC = 'transfer-encoding'.split('').map(getFirstCharCode); +var CC_UPGRADE = 'upgrade'.split('').map(getFirstCharCode); +var CC_CONTLEN = 'content-length'.split('').map(getFirstCharCode); // URI-parsing Regular Expressions ... @@ -65,40 +62,48 @@ var RE_QUERY = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:@/?"\x80-\xFF]|%[0-9A-Fa-f]{2})*/; // Note: fragments are technically not allowed in the request line, but // joyent/http-parser allowed it previously so we do too for better backwards // compatibility ... -var RE_ORIGIN_FORM = new RegExp('(?:' + RE_ABS_PATH.source + '(?:\\?' + RE_QUERY.source + ')?(?:#' + RE_QUERY.source + ')?)'); +var RE_ORIGIN_FORM = new RegExp('(?:' + RE_ABS_PATH.source + '(?:\\?' + + RE_QUERY.source + ')?(?:#' + RE_QUERY.source + + ')?)'); var RE_SCHEME = /[A-Za-z][A-Za-z0-9+\-.]*/; var RE_USERINFO = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:]|%[0-9A-Fa-f]{2})*/; var RE_IPV4_OCTET = /(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/; -var RE_IPV4 = new RegExp('(?:' + RE_IPV4_OCTET.source + '\\.){3}' + RE_IPV4_OCTET.source); +var RE_IPV4 = new RegExp('(?:' + RE_IPV4_OCTET.source + '\\.){3}' + + RE_IPV4_OCTET.source); var RE_H16 = /[0-9A-Fa-f]{1,4}/; -var RE_LS32 = new RegExp('(?:' + RE_H16.source + ':' + RE_H16.source + ')|(?:' + RE_IPV4.source + ')'); +var RE_LS32 = new RegExp('(?:' + RE_H16.source + ':' + RE_H16.source + ')|(?:' + + RE_IPV4.source + ')'); var RE_H16_COLON = new RegExp('(?:' + RE_H16.source + ':)'); -var RE_IPV6 = new RegExp('(?:' - // Begin LS32 postfix cases - + '(?:' - + [ - RE_H16_COLON.source + '{6}', - '::' + RE_H16_COLON.source + '{5}', - '(?:' + RE_H16.source + ')?::' + RE_H16_COLON.source + '{4}', - '(?:' + RE_H16_COLON.source + '{0,1}' + RE_H16.source + ')?::' + RE_H16_COLON.source + '{3}', - '(?:' + RE_H16_COLON.source + '{0,2}' + RE_H16.source + ')?::' + RE_H16_COLON.source + '{2}', - '(?:' + RE_H16_COLON.source + '{0,3}' + RE_H16.source + ')?::' + RE_H16_COLON.source, - '(?:' + RE_H16_COLON.source + '{0,4}' + RE_H16.source + ')?::', - ].join(')|(?:') - + ')(?:' + RE_LS32.source + ')' - // End LS32 postfix cases - + ')' - + '|(?:(?:' + RE_H16_COLON.source + '{0,5}' + RE_H16.source + ')?::' + RE_H16.source + ')' - + '|(?:(?:' + RE_H16_COLON.source + '{0,6}' + RE_H16.source + ')?::)'); - +var RE_IPV6 = new RegExp('(?:' + + // Begin LS32 postfix cases + '(?:' + + [ + RE_H16_COLON.source + '{6}', + '::' + RE_H16_COLON.source + '{5}', + '(?:' + RE_H16.source + ')?::' + RE_H16_COLON.source + '{4}', + '(?:' + RE_H16_COLON.source + '{0,1}' + RE_H16.source + ')?::' + + RE_H16_COLON.source + '{3}', + '(?:' + RE_H16_COLON.source + '{0,2}' + RE_H16.source + ')?::' + + RE_H16_COLON.source + '{2}', + '(?:' + RE_H16_COLON.source + '{0,3}' + RE_H16.source + ')?::' + + RE_H16_COLON.source, + '(?:' + RE_H16_COLON.source + '{0,4}' + RE_H16.source + ')?::', + ].join(')|(?:') + + ')(?:' + RE_LS32.source + ')' + + // End LS32 postfix cases + ')' + + '|(?:(?:' + RE_H16_COLON.source + '{0,5}' + RE_H16.source + ')?::' + + RE_H16.source + ')' + + '|(?:(?:' + RE_H16_COLON.source + '{0,6}' + RE_H16.source + ')?::)'); var RE_REGNAME = /(?:[A-Za-z0-9\-._~!$&'()*+,;=]|%[0-9A-Fa-f]{2})*/; var RE_HOST = new RegExp('(?:(?:\\[' + RE_IPV6.source + '\\])|(?:' + RE_IPV4.source + ')|' + RE_REGNAME.source + ')'); var RE_AUTHORITY = new RegExp('(?:(?:' + RE_USERINFO.source + '@)?' + RE_HOST.source + '(?::[0-9]*)?)'); var RE_PATH_ABEMPTY = new RegExp('(?:/' + RE_PCHAR.source + '*)*'); -var RE_PATH_ROOTLESS = new RegExp('(?:' + RE_PCHAR.source + '+' + RE_PATH_ABEMPTY.source + ')'); +var RE_PATH_ROOTLESS = new RegExp('(?:' + RE_PCHAR.source + '+' + + RE_PATH_ABEMPTY.source + ')'); var RE_PATH_ABSOLUTE = new RegExp('(?:/' + RE_PATH_ROOTLESS.source + '?)'); var RE_HIER_PART = new RegExp('(?:(?://' + RE_AUTHORITY.source + RE_PATH_ABEMPTY.source + ')|' + @@ -107,12 +112,20 @@ var RE_HIER_PART = new RegExp('(?:(?://' + RE_AUTHORITY.source + // Note: fragments are technically not allowed in the request line, but // joyent/http-parser allowed it previously so we do too for better backwards // compatibility ... -var RE_ABSOLUTE_FORM = new RegExp('(?:' + RE_SCHEME.source + ':' + RE_HIER_PART.source + '(?:\\?' + RE_QUERY.source + ')?(?:#' + RE_QUERY.source + ')?)'); - -var RE_REQUEST_TARGET = new RegExp('(?:' + RE_ORIGIN_FORM.source + '|' + RE_ABSOLUTE_FORM.source + '|' + RE_AUTHORITY.source + '|\\*)'); -var RE_REQUEST_LINE = new RegExp('^([!#$%\'*+\\-.^_`|~0-9A-Za-z]+) (' + RE_REQUEST_TARGET.source + ')(?: HTTP\\/1\\.([01]))?$'); +var RE_ABSOLUTE_FORM = new RegExp('(?:' + RE_SCHEME.source + ':' + + RE_HIER_PART.source + '(?:\\?' + + RE_QUERY.source + ')?(?:#' + + RE_QUERY.source + ')?)'); + +var RE_REQUEST_TARGET = new RegExp('(?:' + RE_ORIGIN_FORM.source + '|' + + RE_ABSOLUTE_FORM.source + '|' + + RE_AUTHORITY.source + '|\\*)'); +var RE_REQUEST_LINE = new RegExp('^([!#$%\'*+\\-.^_`|~0-9A-Za-z]+) (' + + RE_REQUEST_TARGET.source + + ')(?: HTTP\\/1\\.([01]))?$'); /* -request-target = origin-form | absolute-form | authority-form | asterisk-form +request-target = origin-form | absolute-form | authority-form | + asterisk-form origin-form = absolute-path [ "?" query ] absolute-path = 1*( "/" segment ) segment = *pchar @@ -120,12 +133,14 @@ request-target = origin-form | absolute-form | authority-form | ast unreserved = ALPHA | DIGIT | "-" | "." | "_" | "~" pct-encoded = "%" HEXDIG HEXDIG HEXDIG = DIGIT | "A" | "B" | "C" | "D" | "E" | "F" - sub-delims = "!" | "$" | "&" | "'" | "(" | ")" | "*" | "+" | "," | ";" | "=" + sub-delims = "!" | "$" | "&" | "'" | "(" | ")" | "*" | "+" | + "," | ";" | "=" query = *( pchar | "/" | "?" ) absolute-form = absolute-URI absolute-URI = scheme ":" hier-part [ "?" query ] scheme = alpha *( alpha | digit | "+" | "-" | "." ) - hier-part = "//" authority path-abempty | path-absolute | path-rootless | path-empty + hier-part = "//" authority path-abempty | path-absolute | + path-rootless | path-empty authority = [ userinfo "@" ] host [ ":" port ] userinfo = *( unreserved | pct-encoded | sub-delims | ":" ) host = IP-literal | IPv4address | reg-name @@ -141,7 +156,8 @@ request-target = origin-form | absolute-form | authority-form | ast | [ *6( h16 ":" ) h16 ] "::" h16 = 1*4HEXDIG ls32 = ( h16 ":" h16 ) | IPv4address - IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet + IPv4address = dec-octet "." dec-octet "." dec-octet "." + dec-octet dec-octet = DIGIT ; 0-9 | %x31-39 DIGIT ; 10-99 | "1" 2DIGIT ; 100-199 @@ -354,7 +370,7 @@ HTTPParser.prototype._processHdrLine = function(line) { if (maxHeaderPairs <= 0 || ++this._nhdrpairs < maxHeaderPairs) headers.push(fieldName, fieldValue); } - break; + break; case STATE_REQ_LINE: // Original HTTP parser ignored blank lines before request/status line, // so we do that here too ... @@ -387,7 +403,7 @@ HTTPParser.prototype._processHdrLine = function(line) { this.httpMinor = parseInt(minor, 10); this._state = STATE_HEADER; } - break; + break; case STATE_STATUS_LINE: // Original HTTP parser ignored blank lines before request/status line, // so we do that here too ... @@ -406,7 +422,7 @@ HTTPParser.prototype._processHdrLine = function(line) { this.statusCode = parseInt(m[2], 10); this.statusText = m[3] || ''; this._state = STATE_HEADER; - break; + break; default: this.execute = this._executeError; this._err = new Error('Unexpected HTTP parser state: ' + this._state); @@ -499,7 +515,6 @@ HTTPParser.prototype._executeHeader = function(data) { var seenCR = this._seenCR; var buf = this._buf; var nhdrbytes = this._nhdrbytes; - var bytesToAdd; var ret; while (offset < len) { @@ -542,7 +557,7 @@ HTTPParser.prototype._executeHeader = function(data) { ret = indexOfCRLF(data, len, offset); if (ret > -1) { // Our internal buffer contains a full line - bytesToAdd = ret - offset; + var bytesToAdd = ret - offset; if (bytesToAdd > 0) { nhdrbytes += bytesToAdd; if (nhdrbytes > MAX_HEADER_BYTES) { @@ -694,7 +709,7 @@ HTTPParser.prototype._executeBodyChunked = function(data) { buf += data.toString('ascii', offset, end); offset = len; // break out of while loop } - break; + break; case STATE_BODY_CHUNKED_BYTES: var dataleft = len - offset; if (dataleft >= nbytes) { @@ -707,7 +722,7 @@ HTTPParser.prototype._executeBodyChunked = function(data) { this.onBody(data, offset, len); offset = len; } - break; + break; case STATE_BODY_CHUNKED_BYTES_CRLF: if (nbytes === 0 && data[offset++] === CR) ++nbytes; @@ -718,7 +733,7 @@ HTTPParser.prototype._executeBodyChunked = function(data) { this._err = new Error('Malformed chunk (missing CRLF)'); return this._err; } - break; + break; default: this.execute = this._executeError; this._err = new Error('Unexpected parser state while reading chunks'); @@ -795,13 +810,14 @@ HTTPParser.prototype._needsEOF = function() { return false; return true; -} +}; -HTTPParser.REQUEST = 0; -HTTPParser.RESPONSE = 1; +var REQUEST = HTTPParser.REQUEST = 0; +var RESPONSE = HTTPParser.RESPONSE = 1; +module.exports = HTTPParser; function indexOfCRLF(buf, buflen, offset) { var bo1; @@ -831,9 +847,9 @@ function readChunkSize(str) { } } if (val === undefined) - return new Error('Invalid chunk size'); - if (val > MAX_CHUNK_SIZE) - return new Error('Chunk size too big'); + val = new Error('Invalid chunk size'); + else if (val > MAX_CHUNK_SIZE) + val = new Error('Chunk size too big'); return val; } @@ -847,9 +863,15 @@ function ltrim(value) { } function trim(value) { var length = value.length, start, end; - for (start = 0; start < length && value.charCodeAt(start) <= 32; start++) { } - for (end = length; end > start && value.charCodeAt(end - 1) <= 32; end--) { } - return start > 0 || end < length ? value.substring(start, end) : ''; + for (start = 0; + start < length && + (value.charCodeAt(start) === 32 || value.charCodeAt(start) === 9); + ++start); + for (end = length; + end > start && + (value.charCodeAt(end - 1) === 32 || value.charCodeAt(end - 1) === 9); + --end); + return start > 0 || end < length ? value.slice(start, end) : value; } function equalsLower(input, ref) { @@ -864,5 +886,6 @@ function equalsLower(input, ref) { return true; } - -module.exports = HTTPParser; \ No newline at end of file +function getFirstCharCode(str) { + return str.charCodeAt(0); +} From 8d56d59ce6a6e27d2bc7cacb74ea352f710d01d0 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 21:03:01 -0400 Subject: [PATCH 32/67] http: add misc notes about differences with joyent/http-parser --- lib/_http_parser.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 62467f477703ce..7e98346326f038 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -1,3 +1,32 @@ +/* +Misc differences with joyent/http-parser: + * Folding whitespace behavior conformant with RFC 7230: + "A user agent that receives an obs-fold in a response message that is + not within a message/http container MUST replace each received + obs-fold with one or more SP octets prior to interpreting the field + value." + + * Optional whitespace is removed before interpreting a header field value, as + suggested by RFC 7230: + "A field value might be preceded and/or followed by optional + whitespace (OWS); a single SP preceding the field-value is preferred + for consistent readability by humans. The field value does not + include any leading or trailing whitespace: OWS occurring before the + first non-whitespace octet of the field value or after the last + non-whitespace octet of the field value ought to be excluded by + parsers when extracting the field value from a header field." + + * Enforces CRLF for line endings instead of additionally allowing just LF + + * Does not allow spaces in header field names + + * Smaller max chunk/content length size (double vs uint64) + + * No special handling for "Proxy-Connection" header. The reason for this is + that "Proxy-Connection" was an experimental header for HTTP 1.0 user agents + that ended up being a bad idea because of the confusion it can bring. +*/ + var inspect = require('util').inspect; var CR = 13; From 2728b9d76ed58ac781bd675e9ffdc36d4f45fbf0 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 21:06:55 -0400 Subject: [PATCH 33/67] test: port main req/res tests from joyent/http-parser --- test/parallel/test-http-parser-durability.js | 1601 ++++++++++++++++++ test/parallel/test-http-parser.js | 5 +- 2 files changed, 1603 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-http-parser-durability.js diff --git a/test/parallel/test-http-parser-durability.js b/test/parallel/test-http-parser-durability.js new file mode 100644 index 00000000000000..acc26670fde4ff --- /dev/null +++ b/test/parallel/test-http-parser-durability.js @@ -0,0 +1,1601 @@ +var assert = require('assert'); +var inspect = require('util').inspect; + +var HTTPParser = require('_http_parser'); + +var CRLF = '\r\n'; +var REQUEST = HTTPParser.REQUEST; +var RESPONSE = HTTPParser.RESPONSE; + +var cases = [ + // REQUESTS ================================================================== + { + name: 'curl get', + type: REQUEST, + raw: [ + 'GET /test HTTP/1.1', + 'User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 ' + + 'OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1', + 'Host: 0.0.0.0=5000', + 'Accept: */*', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/test', + headers: [ + 'User-Agent', + 'curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 ' + + 'OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1', + 'Host', + '0.0.0.0=5000', + 'Accept', + '*/*', + ], + upgrade: false, + body: undefined + }, + { + name: 'firefox get', + type: REQUEST, + raw: [ + 'GET /favicon.ico HTTP/1.1', + 'Host: 0.0.0.0=5000', + 'User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) ' + + 'Gecko/2008061015 Firefox/3.0', + 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language: en-us,en;q=0.5', + 'Accept-Encoding: gzip,deflate', + 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'Keep-Alive: 300', + 'Connection: keep-alive', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/favicon.ico', + headers: [ + 'Host', + '0.0.0.0=5000', + 'User-Agent', + 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) ' + + 'Gecko/2008061015 Firefox/3.0', + 'Accept', + 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language', + 'en-us,en;q=0.5', + 'Accept-Encoding', + 'gzip,deflate', + 'Accept-Charset', + 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'Keep-Alive', + '300', + 'Connection', + 'keep-alive', + ], + upgrade: false, + body: undefined + }, + { + name: 'repeating characters', + type: REQUEST, + raw: [ + 'GET /repeater HTTP/1.1', + 'aaaaaaaaaaaaa:++++++++++', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/repeater', + headers: [ + 'aaaaaaaaaaaaa', + '++++++++++', + ], + upgrade: false, + body: undefined + }, + { + name: 'fragment in url', + type: REQUEST, + raw: [ + 'GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/forums/1/topics/2375?page=1#posts-17408', + headers: [], + upgrade: false, + body: undefined + }, + { + name: 'get no headers no body', + type: REQUEST, + raw: [ + 'GET /get_no_headers_no_body/world HTTP/1.1', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/get_no_headers_no_body/world', + headers: [], + upgrade: false, + body: undefined + }, + { + name: 'get one header no body', + type: REQUEST, + raw: [ + 'GET /get_one_header_no_body HTTP/1.1', + 'Accept: */*', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/get_one_header_no_body', + headers: [ + 'Accept', + '*/*', + ], + upgrade: false, + body: undefined + }, + { + name: 'get funky content length body hello', + type: REQUEST, + raw: [ + 'GET /get_funky_content_length_body_hello HTTP/1.0', + 'conTENT-Length: 5', + '', + 'HELLO' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 0, + method: 'GET', + url: '/get_funky_content_length_body_hello', + headers: [ + 'conTENT-Length', + '5', + ], + upgrade: false, + body: 'HELLO' + }, + { + name: 'post identity body world', + type: REQUEST, + raw: [ + 'POST /post_identity_body_world?q=search#hey HTTP/1.1', + 'Accept: */*', + 'Transfer-Encoding: identity', + 'Content-Length: 5', + '', + 'World' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'POST', + url: '/post_identity_body_world?q=search#hey', + headers: [ + 'Accept', + '*/*', + 'Transfer-Encoding', + 'identity', + 'Content-Length', + '5', + ], + upgrade: false, + body: 'World' + }, + { + name: 'post - chunked body: all your base are belong to us', + type: REQUEST, + raw: [ + 'POST /post_chunked_all_your_base HTTP/1.1', + 'Transfer-Encoding: chunked', + '', + '1e', + 'all your base are belong to us', + '0', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'POST', + url: '/post_chunked_all_your_base', + headers: [ + 'Transfer-Encoding', + 'chunked', + ], + upgrade: false, + body: 'all your base are belong to us' + }, + { + name: 'two chunks ; triple zero ending', + type: REQUEST, + raw: [ + 'POST /two_chunks_mult_zero_end HTTP/1.1', + 'Transfer-Encoding: chunked', + '', + '5', + 'hello', + '6', + ' world', + '000', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'POST', + url: '/two_chunks_mult_zero_end', + headers: [ + 'Transfer-Encoding', + 'chunked', + ], + upgrade: false, + body: 'hello world' + }, + { + name: 'chunked with trailing headers', + type: REQUEST, + raw: [ + 'POST /chunked_w_trailing_headers HTTP/1.1', + 'Transfer-Encoding: chunked', + '', + '5', + 'hello', + '6', + ' world', + '0', + 'Vary: *', + 'Content-Type: text/plain', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'POST', + url: '/chunked_w_trailing_headers', + headers: [ + 'Transfer-Encoding', + 'chunked', + 'Vary', + '*', + 'Content-Type', + 'text/plain', + ], + upgrade: false, + body: 'hello world' + }, + { + name: 'chunked with chunk extensions', + type: REQUEST, + raw: [ + 'POST /chunked_w_extensions HTTP/1.1', + 'Transfer-Encoding: chunked', + '', + '5; woohoo3;whaaaaaaaat=aretheseparametersfor', + 'hello', + '6; blahblah; blah', + ' world', + '0', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'POST', + url: '/chunked_w_extensions', + headers: [ + 'Transfer-Encoding', + 'chunked', + ], + upgrade: false, + body: 'hello world' + }, + { + name: 'with quotes', + type: REQUEST, + raw: [ + 'GET /with_"stupid"_quotes?foo="bar" HTTP/1.1', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/with_"stupid"_quotes?foo="bar"', + headers: [], + upgrade: false, + body: undefined + }, + { + name: 'apachebench get', + type: REQUEST, + raw: [ + 'GET /test HTTP/1.0', + 'Host: 0.0.0.0:5000', + 'User-Agent: ApacheBench/2.3', + 'Accept: */*', + '', '' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 0, + method: 'GET', + url: '/test', + headers: [ + 'Host', + '0.0.0.0:5000', + 'User-Agent', + 'ApacheBench/2.3', + 'Accept', + '*/*', + ], + upgrade: false, + body: undefined + }, + { + name: 'query url with question mark', + type: REQUEST, + raw: [ + 'GET /test.cgi?foo=bar?baz HTTP/1.1', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/test.cgi?foo=bar?baz', + headers: [], + upgrade: false, + body: undefined + }, + { + name: 'newline prefix get', + type: REQUEST, + raw: [ + '', + 'GET /test HTTP/1.1', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/test', + headers: [], + upgrade: false, + body: undefined + }, + { + name: 'upgrade request', + type: REQUEST, + raw: [ + 'GET /demo HTTP/1.1', + 'Host: example.com', + 'Connection: Upgrade', + 'Sec-WebSocket-Key2: 12998 5 Y3 1 .P00', + 'Sec-WebSocket-Protocol: sample', + 'Upgrade: WebSocket', + 'Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5', + 'Origin: http://example.com', + '', '', + 'Hot diggity dogg' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/demo', + headers: [ + 'Host', + 'example.com', + 'Connection', + 'Upgrade', + 'Sec-WebSocket-Key2', + '12998 5 Y3 1 .P00', + 'Sec-WebSocket-Protocol', + 'sample', + 'Upgrade', + 'WebSocket', + 'Sec-WebSocket-Key1', + '4 @1 46546xW%0l 1 5', + 'Origin', + 'http://example.com', + ], + upgrade: true, + body: undefined + }, + { + name: 'connect request', + type: REQUEST, + raw: [ + 'CONNECT 0-home0.netscape.com:443 HTTP/1.0', + 'User-agent: Mozilla/1.1N', + 'Proxy-authorization: basic aGVsbG86d29ybGQ=', + '', '', + 'some data', + 'and yet even more data' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 0, + method: 'CONNECT', + url: '0-home0.netscape.com:443', + headers: [ + 'User-agent', + 'Mozilla/1.1N', + 'Proxy-authorization', + 'basic aGVsbG86d29ybGQ=', + ], + upgrade: true, + body: undefined + }, + { + name: 'report request', + type: REQUEST, + raw: [ + 'REPORT /test HTTP/1.1', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'REPORT', + url: '/test', + headers: [], + upgrade: false, + body: undefined + }, + { + name: 'request with no http version', + type: REQUEST, + raw: [ + 'GET /', + '', '' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: false, + httpMajor: 0, + httpMinor: 9, + method: 'GET', + url: '/', + headers: [], + upgrade: false, + body: undefined + }, + { + name: 'm-search request', + type: REQUEST, + raw: [ + 'M-SEARCH * HTTP/1.1', + 'HOST: 239.255.255.250:1900', + 'MAN: "ssdp:discover"', + 'ST: "ssdp:all"', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'M-SEARCH', + url: '*', + headers: [ + 'HOST', + '239.255.255.250:1900', + 'MAN', + '"ssdp:discover"', + 'ST', + '"ssdp:all"', + ], + upgrade: false, + body: undefined + }, + { + name: 'line folding in header value', + type: REQUEST, + raw: [ + 'GET / HTTP/1.1', + 'Line1: abc', + '\tdef', + ' ghi', + '\t\tjkl', + ' mno ', + '\t \tqrs', + 'Line2: \t line2\t', + 'Line3:', + ' line3', + 'Line4: ', + ' ', + 'Connection:', + ' close', + '', '' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/', + headers: [ + 'Line1', + 'abc def ghi jkl mno qrs', + 'Line2', + 'line2', + 'Line3', + 'line3', + 'Line4', + '', + 'Connection', + 'close', + ], + upgrade: false, + body: undefined + }, + { + name: 'host terminated by a query string', + type: REQUEST, + raw: [ + 'GET http://example.org?hail=all HTTP/1.1', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: 'http://example.org?hail=all', + headers: [], + upgrade: false, + body: undefined + }, + { + name: 'host:port terminated by a query string', + type: REQUEST, + raw: [ + 'GET http://example.org:1234?hail=all HTTP/1.1', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: 'http://example.org:1234?hail=all', + headers: [], + upgrade: false, + body: undefined + }, + { + name: 'host:port terminated by a space', + type: REQUEST, + raw: [ + 'GET http://example.org:1234 HTTP/1.1', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: 'http://example.org:1234', + headers: [], + upgrade: false, + body: undefined + }, + { + name: 'PATCH request', + type: REQUEST, + raw: [ + 'PATCH /file.txt HTTP/1.1', + 'Host: www.example.com', + 'Content-Type: application/example', + 'If-Match: "e0023aa4e"', + 'Content-Length: 10', + '', + 'cccccccccc' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'PATCH', + url: '/file.txt', + headers: [ + 'Host', + 'www.example.com', + 'Content-Type', + 'application/example', + 'If-Match', + '"e0023aa4e"', + 'Content-Length', + '10', + ], + upgrade: false, + body: 'cccccccccc' + }, + { + name: 'connect caps request', + type: REQUEST, + raw: [ + 'CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0', + 'User-agent: Mozilla/1.1N', + 'Proxy-authorization: basic aGVsbG86d29ybGQ=', + '', '' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 0, + method: 'CONNECT', + url: 'HOME0.NETSCAPE.COM:443', + headers: [ + 'User-agent', + 'Mozilla/1.1N', + 'Proxy-authorization', + 'basic aGVsbG86d29ybGQ=', + ], + upgrade: true, + body: undefined + }, + { + name: 'utf-8 path request', + type: REQUEST, + raw: [ + new Buffer('GET /δ¶/δt/pope?q=1#narf HTTP/1.1', 'utf8') + .toString('binary'), + 'Host: github.com', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: new Buffer('/δ¶/δt/pope?q=1#narf', 'utf8').toString('binary'), + headers: [ + 'Host', + 'github.com', + ], + upgrade: false, + body: undefined + }, + { + name: 'hostname underscore', + type: REQUEST, + raw: [ + 'CONNECT home_0.netscape.com:443 HTTP/1.0', + 'User-agent: Mozilla/1.1N', + 'Proxy-authorization: basic aGVsbG86d29ybGQ=', + '', '' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 0, + method: 'CONNECT', + url: 'home_0.netscape.com:443', + headers: [ + 'User-agent', + 'Mozilla/1.1N', + 'Proxy-authorization', + 'basic aGVsbG86d29ybGQ=', + ], + upgrade: true, + body: undefined + }, + { + name: 'eat CRLF between requests, no "Connection: close" header', + type: REQUEST, + raw: [ + 'POST / HTTP/1.1', + 'Host: www.example.com', + 'Content-Type: application/x-www-form-urlencoded', + 'Content-Length: 4', + '', + 'q=42', + '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'POST', + url: '/', + headers: [ + 'Host', + 'www.example.com', + 'Content-Type', + 'application/x-www-form-urlencoded', + 'Content-Length', + '4', + ], + upgrade: false, + body: 'q=42' + }, + { + name: 'eat CRLF between requests even if "Connection: close" is set', + type: REQUEST, + raw: [ + 'POST / HTTP/1.1', + 'Host: www.example.com', + 'Content-Type: application/x-www-form-urlencoded', + 'Content-Length: 4', + 'Connection: close', + '', + 'q=42', + '' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'POST', + url: '/', + headers: [ + 'Host', + 'www.example.com', + 'Content-Type', + 'application/x-www-form-urlencoded', + 'Content-Length', + '4', + 'Connection', + 'close', + ], + upgrade: false, + body: 'q=42' + }, + { + name: 'PURGE request', + type: REQUEST, + raw: [ + 'PURGE /file.txt HTTP/1.1', + 'Host: www.example.com', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'PURGE', + url: '/file.txt', + headers: [ + 'Host', + 'www.example.com', + ], + upgrade: false, + body: undefined + }, + { + name: 'SEARCH request', + type: REQUEST, + raw: [ + 'SEARCH / HTTP/1.1', + 'Host: www.example.com', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'SEARCH', + url: '/', + headers: [ + 'Host', + 'www.example.com', + ], + upgrade: false, + body: undefined + }, + { + name: 'host:port and basic_auth', + type: REQUEST, + raw: [ + 'GET http://a%12:b!&*$@example.org:1234/toto HTTP/1.1', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: 'http://a%12:b!&*$@example.org:1234/toto', + headers: [], + upgrade: false, + body: undefined + }, + { + name: 'multiple connection header values with folding', + type: REQUEST, + raw: [ + 'GET /demo HTTP/1.1', + 'Host: example.com', + 'Connection: Something,', + ' Upgrade, ,Keep-Alive', + 'Sec-WebSocket-Key2: 12998 5 Y3 1 .P00', + 'Sec-WebSocket-Protocol: sample', + 'Upgrade: WebSocket', + 'Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5', + 'Origin: http://example.com', + '', '', + 'Hot diggity dogg' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/demo', + headers: [ + 'Host', + 'example.com', + 'Connection', + 'Something, Upgrade, ,Keep-Alive', + 'Sec-WebSocket-Key2', + '12998 5 Y3 1 .P00', + 'Sec-WebSocket-Protocol', + 'sample', + 'Upgrade', + 'WebSocket', + 'Sec-WebSocket-Key1', + '4 @1 46546xW%0l 1 5', + 'Origin', + 'http://example.com', + ], + upgrade: true, + body: undefined + }, + { + name: 'multiple connection header values with folding and lws', + type: REQUEST, + raw: [ + 'GET /demo HTTP/1.1', + 'Connection: keep-alive, upgrade', + 'Upgrade: WebSocket', + '', '', + 'Hot diggity dogg' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/demo', + headers: [ + 'Connection', + 'keep-alive, upgrade', + 'Upgrade', + 'WebSocket', + ], + upgrade: true, + body: undefined + }, + { + name: 'multiple connection header values with folding and lws and CRLF', + type: REQUEST, + raw: [ + 'GET /demo HTTP/1.1', + 'Connection: keep-alive, ', + ' upgrade', + 'Upgrade: WebSocket', + '', '', + 'Hot diggity dogg' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/demo', + headers: [ + 'Connection', + 'keep-alive, upgrade', + 'Upgrade', + 'WebSocket', + ], + upgrade: true, + body: undefined + }, + // RESPONSES ================================================================= + { + name: 'google 301', + type: RESPONSE, + raw: [ + 'HTTP/1.1 301 Moved Permanently', + 'Location: http://www.google.com/', + 'Content-Type: text/html; charset=UTF-8', + 'Date: Sun, 26 Apr 2009 11:11:49 GMT', + 'Expires: Tue, 26 May 2009 11:11:49 GMT', + 'X-$PrototypeBI-Version: 1.6.0.3', + 'Cache-Control: public, max-age=2592000', + 'Server: gws', + 'Content-Length: 219 ', + '', + '\n301 Moved\n

301 ' + + 'Moved

\nThe document has moved\nhere.\r\n\r\n' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + statusCode: 301, + statusText: 'Moved Permanently', + headers: [ + 'Location', + 'http://www.google.com/', + 'Content-Type', + 'text/html; charset=UTF-8', + 'Date', + 'Sun, 26 Apr 2009 11:11:49 GMT', + 'Expires', + 'Tue, 26 May 2009 11:11:49 GMT', + 'X-$PrototypeBI-Version', + '1.6.0.3', + 'Cache-Control', + 'public, max-age=2592000', + 'Server', + 'gws', + 'Content-Length', + '219', + ], + upgrade: false, + body: '\n301 Moved\n

301 ' + + 'Moved

\nThe document has moved\nhere.\r\n\r\n' + }, + { + name: 'no content-length response', + type: RESPONSE, + raw: [ + 'HTTP/1.1 200 OK', + 'Date: Tue, 04 Aug 2009 07:59:32 GMT', + 'Server: Apache', + 'X-Powered-By: Servlet/2.5 JSP/2.1', + 'Content-Type: text/xml; charset=utf-8', + 'Connection: close', + '', + '\n\n ' + + '\n \n SOAP-ENV:' + + 'Client\n Client Error\n' + + ' \n \n' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: true, + httpMajor: 1, + httpMinor: 1, + statusCode: 200, + statusText: 'OK', + headers: [ + 'Date', + 'Tue, 04 Aug 2009 07:59:32 GMT', + 'Server', + 'Apache', + 'X-Powered-By', + 'Servlet/2.5 JSP/2.1', + 'Content-Type', + 'text/xml; charset=utf-8', + 'Connection', + 'close', + ], + upgrade: false, + body: '\n\n ' + + '\n \n SOAP-ENV:' + + 'Client\n Client Error\n' + + ' \n \n' + }, + { + name: '404 no headers no body', + type: RESPONSE, + raw: [ + 'HTTP/1.1 404 Not Found', + '', '' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: true, + httpMajor: 1, + httpMinor: 1, + statusCode: 404, + statusText: 'Not Found', + headers: [], + upgrade: false, + body: undefined + }, + { + name: '301 no response phrase', + type: RESPONSE, + raw: [ + 'HTTP/1.1 301', + '', '' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: true, + httpMajor: 1, + httpMinor: 1, + statusCode: 301, + statusText: '', + headers: [], + upgrade: false, + body: undefined + }, + { + name: '200 trailing space on chunked body', + type: RESPONSE, + raw: [ + 'HTTP/1.1 200 OK', + 'Content-Type: text/plain', + 'Transfer-Encoding: chunked', + '', + '25 ', + 'This is the data in the first chunk\r\n', + '1C', + 'and this is the second one\r\n', + '0 ', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + statusCode: 200, + statusText: 'OK', + headers: [ + 'Content-Type', + 'text/plain', + 'Transfer-Encoding', + 'chunked', + ], + upgrade: false, + body: 'This is the data in the first chunk\r\n' + + 'and this is the second one\r\n' + }, + { + name: 'underscore header key', + type: RESPONSE, + raw: [ + 'HTTP/1.1 200 OK', + 'Server: DCLK-AdSvr', + 'Content-Type: text/xml', + 'Content-Length: 0', + 'DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;' + + 'dcmt=text/xml;;~cs=o', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + statusCode: 200, + statusText: 'OK', + headers: [ + 'Server', + 'DCLK-AdSvr', + 'Content-Type', + 'text/xml', + 'Content-Length', + '0', + 'DCLK_imp', + 'v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;' + + 'dcmt=text/xml;;~cs=o', + ], + upgrade: false, + body: undefined + }, + { + name: 'no merge with empty value', + type: RESPONSE, + raw: [ + 'HTTP/1.0 301 Moved Permanently', + 'Date: Thu, 03 Jun 2010 09:56:32 GMT', + 'Server: Apache/2.2.3 (Red Hat)', + 'Cache-Control: public', + 'Pragma: ', + 'Location: http://www.example.org/', + 'Vary: Accept-Encoding', + 'Content-Length: 0', + 'Content-Type: text/html; charset=UTF-8', + 'Connection: keep-alive', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 0, + statusCode: 301, + statusText: 'Moved Permanently', + headers: [ + 'Date', + 'Thu, 03 Jun 2010 09:56:32 GMT', + 'Server', + 'Apache/2.2.3 (Red Hat)', + 'Cache-Control', + 'public', + 'Pragma', + '', + 'Location', + 'http://www.example.org/', + 'Vary', + 'Accept-Encoding', + 'Content-Length', + '0', + 'Content-Type', + 'text/html; charset=UTF-8', + 'Connection', + 'keep-alive', + ], + upgrade: false, + body: undefined + }, + { + name: 'field underscore', + type: RESPONSE, + raw: [ + 'HTTP/1.1 200 OK', + 'Date: Tue, 28 Sep 2010 01:14:13 GMT', + 'Server: Apache', + 'Cache-Control: no-cache, must-revalidate', + 'Expires: Mon, 26 Jul 1997 05:00:00 GMT', + '.et-Cookie: ExampleCS=1274804622353690521; path=/; domain=.example.com', + 'Vary: Accept-Encoding', + '_eep-Alive: timeout=45', + '_onnection: Keep-Alive', + 'Transfer-Encoding: chunked', + 'Content-Type: text/html', + 'Connection: close', + '', + '0', + '', '' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + statusCode: 200, + statusText: 'OK', + headers: [ + 'Date', + 'Tue, 28 Sep 2010 01:14:13 GMT', + 'Server', + 'Apache', + 'Cache-Control', + 'no-cache, must-revalidate', + 'Expires', + 'Mon, 26 Jul 1997 05:00:00 GMT', + '.et-Cookie', + 'ExampleCS=1274804622353690521; path=/; domain=.example.com', + 'Vary', + 'Accept-Encoding', + '_eep-Alive', + 'timeout=45', + '_onnection', + 'Keep-Alive', + 'Transfer-Encoding', + 'chunked', + 'Content-Type', + 'text/html', + 'Connection', + 'close', + ], + upgrade: false, + body: undefined + }, + { + name: 'non-ASCII in status line', + type: RESPONSE, + raw: [ + 'HTTP/1.1 500 Oriëntatieprobleem', + 'Date: Fri, 5 Nov 2010 23:07:12 GMT+2', + 'Content-Length: 0', + 'Connection: close', + '', '' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + statusCode: 500, + statusText: 'Oriëntatieprobleem', + headers: [ + 'Date', + 'Fri, 5 Nov 2010 23:07:12 GMT+2', + 'Content-Length', + '0', + 'Connection', + 'close', + ], + upgrade: false, + body: undefined + }, + { + name: 'neither content-length nor transfer-encoding response', + type: RESPONSE, + raw: [ + 'HTTP/1.1 200 OK', + 'Content-Type: text/plain', + '', + 'hello world' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: true, + httpMajor: 1, + httpMinor: 1, + statusCode: 200, + statusText: 'OK', + headers: [ + 'Content-Type', + 'text/plain', + ], + upgrade: false, + body: 'hello world' + }, + { + name: 'HTTP/1.0 with keep-alive and EOF-terminated 200 status', + type: RESPONSE, + raw: [ + 'HTTP/1.0 200 OK', + 'Connection: keep-alive', + '', '' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: true, + httpMajor: 1, + httpMinor: 0, + statusCode: 200, + statusText: 'OK', + headers: [ + 'Connection', + 'keep-alive', + ], + upgrade: false, + body: undefined + }, + { + name: 'HTTP/1.0 with keep-alive and a 204 status', + type: RESPONSE, + raw: [ + 'HTTP/1.0 204 No content', + 'Connection: keep-alive', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 0, + statusCode: 204, + statusText: 'No content', + headers: [ + 'Connection', + 'keep-alive', + ], + upgrade: false, + body: undefined + }, + { + name: 'HTTP/1.1 with an EOF-terminated 200 status', + type: RESPONSE, + raw: [ + 'HTTP/1.1 200 OK', + '', '' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: true, + httpMajor: 1, + httpMinor: 1, + statusCode: 200, + statusText: 'OK', + headers: [], + upgrade: false, + body: undefined + }, + { + name: 'HTTP/1.1 with a 204 status', + type: RESPONSE, + raw: [ + 'HTTP/1.1 204 No content', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + statusCode: 204, + statusText: 'No content', + headers: [], + upgrade: false, + body: undefined + }, + { + name: 'HTTP/1.1 with a 204 status and keep-alive disabled', + type: RESPONSE, + raw: [ + 'HTTP/1.1 204 No content', + 'Connection: close', + '', '' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + statusCode: 204, + statusText: 'No content', + headers: [ + 'Connection', + 'close', + ], + upgrade: false, + body: undefined + }, + { + name: 'HTTP/1.1 with chunked endocing and a 200 response', + type: RESPONSE, + raw: [ + 'HTTP/1.1 200 OK', + 'Transfer-Encoding: chunked', + '', + '0', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + statusCode: 200, + statusText: 'OK', + headers: [ + 'Transfer-Encoding', + 'chunked', + ], + upgrade: false, + body: undefined + }, + { + name: 'newline chunk', + type: RESPONSE, + raw: [ + 'HTTP/1.1 301 MovedPermanently', + 'Date: Wed, 15 May 2013 17:06:33 GMT', + 'Server: Server', + 'x-amz-id-1: 0GPHKXSJQ826RK7GZEB2', + 'p3p: policyref="http://www.amazon.com/w3c/p3p.xml",CP="CAO DSP LAW CUR ' + + 'ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN ' + + 'COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC "', + 'x-amz-id-2: STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNc' + + 'x4oAD', + 'Location: http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al' + + '1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd' + + '_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=126334' + + '0922&pf_rd_i=507846', + 'Vary: Accept-Encoding,User-Agent', + 'Content-Type: text/html; charset=ISO-8859-1', + 'Transfer-Encoding: chunked', + '', + '1', + '\n', + '0', + '', '' + ].join(CRLF), + shouldKeepAlive: true, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + statusCode: 301, + statusText: 'MovedPermanently', + headers: [ + 'Date', + 'Wed, 15 May 2013 17:06:33 GMT', + 'Server', + 'Server', + 'x-amz-id-1', + '0GPHKXSJQ826RK7GZEB2', + 'p3p', + 'policyref="http://www.amazon.com/w3c/p3p.xml",CP="CAO DSP LAW CUR ' + + 'ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN ' + + 'COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC "', + 'x-amz-id-2', + 'STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNc' + + 'x4oAD', + 'Location', + 'http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al' + + '1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd' + + '_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=126334' + + '0922&pf_rd_i=507846', + 'Vary', + 'Accept-Encoding,User-Agent', + 'Content-Type', + 'text/html; charset=ISO-8859-1', + 'Transfer-Encoding', + 'chunked', + ], + upgrade: false, + body: '\n' + }, + { + name: 'empty reason phrase after space', + type: RESPONSE, + raw: [ + 'HTTP/1.1 200 ', + '', '' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: true, + httpMajor: 1, + httpMinor: 1, + statusCode: 200, + statusText: '', + headers: [], + upgrade: false, + body: undefined + }, +]; + + +// Prevent EE warnings since we have many test cases which attach `exit` event +// handlers +process.setMaxListeners(0); + +cases.forEach(function(testCase) { + var parser = new HTTPParser(testCase.type); + var reqEvents = [ 'onHeaders' ]; + var completed = false; + var allHeaders; + var body; + + if (testCase.body !== undefined) + reqEvents.push('onBody'); + + function onHeaders(versionMajor, versionMinor, headers, method, url, + statusCode, statusText, upgrade, shouldKeepAlive) { + assert.strictEqual(reqEvents[0], + 'onHeaders', + 'Expected onHeaders to the next event for: ' + + testCase.name); + reqEvents.shift(); + _assert(assert.strictEqual, + 'versionMajor', + testCase, + versionMajor, + testCase.httpMajor); + _assert(assert.strictEqual, + 'versionMinor', + testCase, + versionMinor, + testCase.httpMinor); + // Defer checking headers in case there are trailers ... + allHeaders = headers; + if (testCase.type === REQUEST) { + _assert(assert.strictEqual, + 'method', + testCase, + method, + testCase.method); + _assert(assert.strictEqual, + 'url', + testCase, + url, + testCase.url); + } else { + _assert(assert.strictEqual, + 'statusCode', + testCase, + statusCode, + testCase.statusCode); + _assert(assert.strictEqual, + 'statusText', + testCase, + statusText, + testCase.statusText); + } + _assert(assert.strictEqual, + 'upgrade', + testCase, + upgrade, + testCase.upgrade); + _assert(assert.strictEqual, + 'shouldKeepAlive', + testCase, + shouldKeepAlive, + testCase.shouldKeepAlive); + } + + function onBody(data, offset, len) { + if (body === undefined) { + assert.strictEqual(reqEvents[0], + 'onBody', + 'Expected onBody to be the next event for: ' + + testCase.name); + reqEvents.shift(); + body = data.toString('binary', offset, offset + len); + } else + body += data.toString('binary', offset, offset + len); + } + + function onComplete() { + assert.strictEqual(reqEvents.length, + 0, + 'Missed ' + reqEvents + ' event(s) for: ' + + testCase.name); + if (parser.headers.length > 0) + allHeaders = allHeaders.concat(parser.headers); + _assert(assert.deepEqual, + 'headers', + testCase, + allHeaders, + testCase.headers); + _assert(assert.strictEqual, + 'body', + testCase, + body, + testCase.body); + completed = true; + } + + parser.onHeaders = onHeaders; + parser.onBody = onBody; + parser.onComplete = onComplete; + + process.on('exit', function() { + assert.strictEqual(completed, + true, + 'Parsing did not complete for: ' + testCase.name); + }); + + try { + var ret = parser.execute(new Buffer(testCase.raw, 'binary')); + parser.finish(); + } catch (ex) { + throw new Error('Unexpected error thrown for: ' + testCase.name + ':\n\n' + + ex.stack + '\n'); + } + if (testCase.error !== undefined && typeof ret === 'number') + throw new Error('Expected error for: ' + testCase.name); + else if (testCase.error === undefined && typeof ret !== 'number') { + throw new Error('Unexpected error for: ' + testCase.name + ':\n\n' + + ret.stack + '\n'); + } +}); + +function _assert(assertFn, type, testCase, actual, expected) { + assertFn(actual, + expected, + type + ' mismatch for: ' + testCase.name + '\nActual:\n' + + inspect(actual) + '\nExpected:\n' + inspect(expected) + '\n'); +} diff --git a/test/parallel/test-http-parser.js b/test/parallel/test-http-parser.js index 544fc8a6cf486e..659f047bdd3c4f 100644 --- a/test/parallel/test-http-parser.js +++ b/test/parallel/test-http-parser.js @@ -9,9 +9,8 @@ var REQUEST = HTTPParser.REQUEST; var RESPONSE = HTTPParser.RESPONSE; // The purpose of this test is not to check HTTP compliance but to test the -// binding. Tests for pathological http messages should be submitted -// upstream to https://github.com/joyent/http-parser for inclusion into -// deps/http-parser/test.c +// binding. Tests for pathological http messages should be added to +// test-http-parser-durability.js function newParser(type) { From f35d9dd8e8f954c6271fb4fcd842942f10c5c9e7 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 15 Apr 2015 21:08:12 -0400 Subject: [PATCH 34/67] benchmark: style change --- benchmark/http/parser.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/benchmark/http/parser.js b/benchmark/http/parser.js index 5795844ee707fe..8107a3a52aa2b8 100644 --- a/benchmark/http/parser.js +++ b/benchmark/http/parser.js @@ -124,9 +124,8 @@ var inputs = { , }; -function onHeadersComplete(versionMajor, versionMinor, headers, method, - url, statusCode, statusMessage, upgrade, - shouldKeepAlive) { +function onHeaders(versionMajor, versionMinor, headers, method, url, statusCode, + statusMessage, upgrade, shouldKeepAlive) { } function onBody(data, start, len) { } @@ -144,7 +143,7 @@ function main(conf) { chunks[i] = new Buffer(chunks[i], 'binary'); var parser = new HTTPParser(kind); - parser.onHeaders = onHeadersComplete; + parser.onHeaders = onHeaders; parser.onBody = onBody; parser.onComplete = onComplete; From fb64bd1d0396cabcbc21f7f242017342c5685903 Mon Sep 17 00:00:00 2001 From: Brian White Date: Thu, 16 Apr 2015 12:03:04 -0400 Subject: [PATCH 35/67] http: reduce error setting boilerplate --- lib/_http_parser.js | 86 +++++++++++++++------------------------------ 1 file changed, 28 insertions(+), 58 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 7e98346326f038..4fbed68ce017d3 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -294,14 +294,18 @@ HTTPParser.prototype.finish = function() { state !== STATE_REQ_LINE && state !== STATE_STATUS_LINE && state !== STATE_COMPLETE) { - this.execute = this._executeError; - this._err = new Error('Invalid EOF state'); - return this._err; + return this._setError('Invalid EOF state'); } }; HTTPParser.prototype.close = function() {}; HTTPParser.prototype.pause = function() {}; HTTPParser.prototype.resume = function() {}; +HTTPParser.prototype._setError = function(msg) { + var err = new Error(msg); + this.execute = this._executeError; + this._err = err; + return err; +}; HTTPParser.prototype._processHdrLine = function(line) { switch (this._state) { case STATE_HEADER: @@ -317,11 +321,8 @@ HTTPParser.prototype._processHdrLine = function(line) { var m = RE_HEADER.exec(line); if (m === null) { var firstChr = line.charCodeAt(0); - if (firstChr !== 32 & firstChr !== 9) { - this.execute = this._executeError; - this._err = new Error('Malformed header line'); - return this._err; - } + if (firstChr !== 32 & firstChr !== 9) + return this._setError('Malformed header line'); // RFC 7230 compliant, but less backwards compatible: var extra = ltrim(line); if (extra.length > 0) { @@ -348,11 +349,8 @@ HTTPParser.prototype._processHdrLine = function(line) { this._flags |= FLAG_UPGRADE; } else if (equalsLower(fieldName, CC_CONTLEN)) { var val = parseInt(fieldValue, 10); - if (val !== val || val > MAX_CHUNK_SIZE) { - this.execute = this._executeError; - this._err = new Error('Bad Content-Length: ' + inspect(val)); - return this._err; - } + if (val !== val || val > MAX_CHUNK_SIZE) + return this._setError('Bad Content-Length: ' + inspect(val)); this._contentLen = val; } headers[headerslen - 1] = fieldValue; @@ -388,11 +386,8 @@ HTTPParser.prototype._processHdrLine = function(line) { this._flags |= FLAG_UPGRADE; } else if (equalsLower(fieldName, CC_CONTLEN)) { var val = parseInt(fieldValue, 10); - if (val !== val || val > MAX_CHUNK_SIZE) { - this.execute = this._executeError; - this._err = new Error('Bad Content-Length: ' + inspect(val)); - return this._err; - } + if (val !== val || val > MAX_CHUNK_SIZE) + return this._setError('Bad Content-Length: ' + inspect(val)); this._contentLen = val; } var maxHeaderPairs = this.maxHeaderPairs; @@ -406,11 +401,8 @@ HTTPParser.prototype._processHdrLine = function(line) { if (line.length === 0) return true; var m = RE_REQUEST_LINE.exec(line); - if (m === null) { - this.execute = this._executeError; - this._err = new Error('Malformed request line'); - return this._err; - } + if (m === null) + return this._setError('Malformed request line'); // m[1]: HTTP method // m[2]: request target // m[3]: HTTP minor version @@ -420,11 +412,8 @@ HTTPParser.prototype._processHdrLine = function(line) { this.url = m[2]; if (minor === undefined) { // HTTP/0.9 ugh... - if (method !== 'GET') { - this.execute = this._executeError; - this._err = new Error('Malformed request line'); - return this._err; - } + if (method !== 'GET') + return this._setError('Malformed request line'); this.httpMajor = 0; this.httpMinor = 9; this._headersEnd(); @@ -439,11 +428,8 @@ HTTPParser.prototype._processHdrLine = function(line) { if (line.length === 0) return true; var m = RE_STATUS_LINE.exec(line); - if (m === null) { - this.execute = this._executeError; - this._err = new Error('Malformed status line'); - return this._err; - } + if (m === null) + return this._setError('Malformed status line'); // m[1]: HTTP minor version // m[2]: HTTP status code // m[3]: Reason text @@ -453,9 +439,7 @@ HTTPParser.prototype._processHdrLine = function(line) { this._state = STATE_HEADER; break; default: - this.execute = this._executeError; - this._err = new Error('Unexpected HTTP parser state: ' + this._state); - return this._err; + return this._setError('Unexpected HTTP parser state: ' + this._state); } }; HTTPParser.prototype._headersEnd = function() { @@ -526,11 +510,8 @@ HTTPParser.prototype._executeStartLine = function(data) { if (data.length === 0) return 0; var firstByte = data[0]; - if ((firstByte < 32 || firstByte >= 127) && firstByte !== CR) { - this.execute = this._executeError; - this._err = new Error('Invalid byte(s) in start line'); - return this._err; - } + if ((firstByte < 32 || firstByte >= 127) && firstByte !== CR) + return this._setError('Invalid byte(s) in start line'); this.execute = this._executeHeader; return this.execute(data); }; @@ -576,10 +557,8 @@ HTTPParser.prototype._executeHeader = function(data) { buf += '\r'; ++nhdrbytes; if (nhdrbytes > MAX_HEADER_BYTES) { - this.execute = this._executeError; - this._err = new Error('Header size limit exceeded (' + + return this._setError('Header size limit exceeded (' + MAX_HEADER_BYTES + ')'); - return this._err; } } } @@ -590,10 +569,8 @@ HTTPParser.prototype._executeHeader = function(data) { if (bytesToAdd > 0) { nhdrbytes += bytesToAdd; if (nhdrbytes > MAX_HEADER_BYTES) { - this.execute = this._executeError; - this._err = new Error('Header size limit exceeded (' + + return this._setError('Header size limit exceeded (' + MAX_HEADER_BYTES + ')'); - return this._err; } buf += data.toString('binary', offset, ret); } @@ -629,10 +606,8 @@ HTTPParser.prototype._executeHeader = function(data) { nhdrbytes += end - offset; if (nhdrbytes > MAX_HEADER_BYTES) { - this.execute = this._executeError; - this._err = new Error('Header size limit exceeded (' + + return this._setError('Header size limit exceeded (' + MAX_HEADER_BYTES + ')'); - return this._err; } buf += data.toString('binary', offset, end); break; @@ -757,16 +732,11 @@ HTTPParser.prototype._executeBodyChunked = function(data) { ++nbytes; else if (nbytes === 1 && data[offset++] === LF) this._state = STATE_BODY_CHUNKED_SIZE; - else { - this.execute = this._executeError; - this._err = new Error('Malformed chunk (missing CRLF)'); - return this._err; - } + else + return this._setError('Malformed chunk (missing CRLF)'); break; default: - this.execute = this._executeError; - this._err = new Error('Unexpected parser state while reading chunks'); - return this._err; + return this._setError('Unexpected parser state while reading chunks'); } } From 9bc7d5c2d61fb5b4edea13a832077abf6eb56c36 Mon Sep 17 00:00:00 2001 From: Brian White Date: Thu, 16 Apr 2015 12:06:44 -0400 Subject: [PATCH 36/67] http: use max js int value - 1 for content/chunk lengths Before this it was possible that a Content-Length or chunk length of 9007199254740993 to be interpreted as 9007199254740992, making it impossible to know that the value was larger than our maximum. Using maximum js int value - 1 allows us to reliably detect when the max length has been exceeded. --- lib/_http_parser.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 4fbed68ce017d3..c5ee45c12c8dbc 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -32,7 +32,8 @@ var inspect = require('util').inspect; var CR = 13; var LF = 10; -var MAX_CHUNK_SIZE = 9007199254740992; +var MAX_CHUNK_SIZE = Number.MAX_SAFE_INTEGER; // 9007199254740991 + var UNHEX = { 48: 0, 49: 1, @@ -57,6 +58,7 @@ var UNHEX = { 101: 14, 102: 15 }; + // RFC 7230 recommends at least 8000 max bytes for request line, but no // recommendation for status lines var MAX_HEADER_BYTES = 80 * 1024; From 686ced18ebf9860e023834da0c9dff079ee4db29 Mon Sep 17 00:00:00 2001 From: Brian White Date: Thu, 16 Apr 2015 12:08:28 -0400 Subject: [PATCH 37/67] http: remove unnecessary internal state variable The folded state is no longer needed since we are now always deferring trimming of header values until the next new header or the end of the headers, whichever comes first. --- lib/_http_parser.js | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index c5ee45c12c8dbc..0f1faed4298f93 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -268,7 +268,6 @@ HTTPParser.prototype.reinitialize = function(type) { this._nbytes = 0; this._nhdrbytes = 0; this._nhdrpairs = 0; - this._folded = false; this._buf = ''; this._seenCR = false; @@ -328,12 +327,8 @@ HTTPParser.prototype._processHdrLine = function(line) { // RFC 7230 compliant, but less backwards compatible: var extra = ltrim(line); if (extra.length > 0) { - if (headerslen === 0) { - this.execute = this._executeError; - this._err = new Error('Malformed header line'); - return this._err; - } - this._folded = true; + if (headerslen === 0) + return this._setError('Malformed header line'); fieldName = headers[headerslen - 2]; fieldValue = headers[headerslen - 1] + ' ' + extra; // Need to re-check value since matched values may now exist ... @@ -360,15 +355,7 @@ HTTPParser.prototype._processHdrLine = function(line) { } else { // Ensures that trailing whitespace after the last folded line for // header values gets trimmed - if (this._folded) { - this._folded = false; - if (headerslen === 0) { - this.execute = this._executeError; - this._err = new Error('Malformed header line'); - return this._err; - } - headers[headerslen - 1] = trim(headers[headerslen - 1]); - } else if (headerslen > 0) + if (headerslen > 0) headers[headerslen - 1] = trim(headers[headerslen - 1]); // m[1]: field name // m[2]: field value @@ -474,8 +461,6 @@ HTTPParser.prototype._headersEnd = function() { var headers = this.headers; var headerslen = headers.length; if ((flags & FLAG_TRAILING) > 0) { - if (this._folded) - this._folded = false; if (headerslen > 0) headers[headerslen - 1] = trim(headers[headerslen - 1]); this.onComplete && this.onComplete(); @@ -484,8 +469,6 @@ HTTPParser.prototype._headersEnd = function() { } else { this.headers = []; if (this.onHeaders) { - if (this._folded) - this._folded = false; if (headerslen > 0) headers[headerslen - 1] = trim(headers[headerslen - 1]); ret = this.onHeaders(httpMajor, httpMinor, headers, method, From b53d2852cc1148c45a2f0be02901ea07004e69cd Mon Sep 17 00:00:00 2001 From: Brian White Date: Thu, 16 Apr 2015 12:08:36 -0400 Subject: [PATCH 38/67] http: lint --- lib/_http_parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 0f1faed4298f93..ef83b953a1aa61 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -790,7 +790,7 @@ HTTPParser.prototype._needsEOF = function() { return false; } - if ((flags & FLAG_CHUNKED) > 0 || this._contentLen != undefined) + if ((flags & FLAG_CHUNKED) > 0 || this._contentLen !== undefined) return false; return true; From db6781400b84099da8973248ec7d92ebb21dc500 Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 17 Apr 2015 01:24:45 -0400 Subject: [PATCH 39/67] http: update parser header comments --- lib/_http_parser.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index ef83b953a1aa61..083def9be10d12 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -1,13 +1,18 @@ /* Misc differences with joyent/http-parser: - * Folding whitespace behavior conformant with RFC 7230: + * Folding whitespace behavior is conformant with RFC 7230: + "A user agent that receives an obs-fold in a response message that is not within a message/http container MUST replace each received obs-fold with one or more SP octets prior to interpreting the field value." + It should also be noted that RFC 7230 now deprecates line folding for HTTP + parsing, FWIW. This parser replaces folds with a single SP octet. + * Optional whitespace is removed before interpreting a header field value, as suggested by RFC 7230: + "A field value might be preceded and/or followed by optional whitespace (OWS); a single SP preceding the field-value is preferred for consistent readability by humans. The field value does not @@ -16,15 +21,22 @@ Misc differences with joyent/http-parser: non-whitespace octet of the field value ought to be excluded by parsers when extracting the field value from a header field." - * Enforces CRLF for line endings instead of additionally allowing just LF + joyent/http-parser keeps trailing whitespace. This parser keeps neither + preceding nor trailing whitespace. + + * Enforces CRLF for line endings instead of additionally allowing just LF. - * Does not allow spaces in header field names + * Does not allow spaces (which are invalid) in header field names. - * Smaller max chunk/content length size (double vs uint64) + * Smaller maximum chunk/content length (2^53-1 vs 2^64-2). Obviously it's + not *impossible* to handle a full 64-bit length, but it would mean adding + some kind of "big integer" library for lengths > 2^53-1. - * No special handling for "Proxy-Connection" header. The reason for this is - that "Proxy-Connection" was an experimental header for HTTP 1.0 user agents - that ended up being a bad idea because of the confusion it can bring. + * No special handling for `Proxy-Connection` header. The reason for this is + that `Proxy-Connection` was an experimental header for HTTP 1.0 user agents + that ended up being a bad idea because of the confusion it can bring. You + can read a bit more about this in + [RFC 7230 A.1.2](https://tools.ietf.org/html/rfc7230#page-79) */ var inspect = require('util').inspect; From 7f8bf45d1c82a1830538c2bfb4bfad9249519bbf Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 17 Apr 2015 01:27:21 -0400 Subject: [PATCH 40/67] http: fix keepalive support This mainly fixes parsing of successive similar message types when there is a fixed-length body. --- lib/_http_parser.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 083def9be10d12..6f6a065c767c18 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -277,6 +277,7 @@ HTTPParser.prototype.reinitialize = function(type) { this._err = undefined; this._flags = 0; this._contentLen = undefined; + this._keepalive = false; this._nbytes = 0; this._nhdrbytes = 0; this._nhdrpairs = 0; @@ -452,7 +453,8 @@ HTTPParser.prototype._headersEnd = function() { var contentLen = this._contentLen; var httpMajor = this.httpMajor; var httpMinor = this.httpMinor; - var keepalive = this._shouldKeepAlive(httpMajor, httpMinor, flags); + var keepalive = this._keepalive = this._shouldKeepAlive(httpMajor, httpMinor, + flags); var ret; this._buf = ''; @@ -476,7 +478,10 @@ HTTPParser.prototype._headersEnd = function() { if (headerslen > 0) headers[headerslen - 1] = trim(headers[headerslen - 1]); this.onComplete && this.onComplete(); - this.reinitialize(type); + if (!keepalive) + this._state = STATE_COMPLETE; + else + this.reinitialize(type); return; } else { this.headers = []; @@ -751,9 +756,18 @@ HTTPParser.prototype._executeBodyLiteral = function(data) { var nbytes = this._contentLen; if (len >= nbytes) { - this.reinitialize(this.type); this.onBody(data, 0, nbytes); this.onComplete && this.onComplete(); + if (this._keepalive) { + this.reinitialize(this.type); + if (len > nbytes) { + ret = this.execute(data.slice(nbytes)); + if (typeof ret !== 'number') + return ret; + return nbytes + ret; + } + } else + this._state = STATE_COMPLETE; return nbytes; } else { this._contentLen -= len; From b0b095064bf6cb1b7ac29d0684d11b5baf93cfac Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 17 Apr 2015 01:28:22 -0400 Subject: [PATCH 41/67] http: onBody needs byte count not offset for third argument --- lib/_http_parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 6f6a065c767c18..3a0ebf2782af38 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -725,7 +725,7 @@ HTTPParser.prototype._executeBodyChunked = function(data) { this._state = STATE_BODY_CHUNKED_BYTES_CRLF; } else { nbytes -= dataleft; - this.onBody(data, offset, len); + this.onBody(data, offset, dataleft); offset = len; } break; From 543afe7a3c6253babc75e69ba361cd5ffe1459a9 Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 17 Apr 2015 15:28:34 -0400 Subject: [PATCH 42/67] http: return data length for non-keepalive literal body --- lib/_http_parser.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 3a0ebf2782af38..adb013780afb0f 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -768,12 +768,11 @@ HTTPParser.prototype._executeBodyLiteral = function(data) { } } else this._state = STATE_COMPLETE; - return nbytes; } else { this._contentLen -= len; this.onBody(data, 0, len); - return len; } + return len; }; HTTPParser.prototype._executeBodyEOF = function(data) { var len = data.length; From 05c3baad9dfb507b4a03294e4b75fde64f0ed2c3 Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 17 Apr 2015 15:32:37 -0400 Subject: [PATCH 43/67] test: port remaining joyent/http-parser tests --- test/parallel/test-http-parser-durability.js | 836 ++++++++++++++++--- 1 file changed, 702 insertions(+), 134 deletions(-) diff --git a/test/parallel/test-http-parser-durability.js b/test/parallel/test-http-parser-durability.js index acc26670fde4ff..99aadb5b760da1 100644 --- a/test/parallel/test-http-parser-durability.js +++ b/test/parallel/test-http-parser-durability.js @@ -1,11 +1,13 @@ var assert = require('assert'); var inspect = require('util').inspect; +var format = require('util').format; var HTTPParser = require('_http_parser'); var CRLF = '\r\n'; var REQUEST = HTTPParser.REQUEST; var RESPONSE = HTTPParser.RESPONSE; +var requestsEnd = -1; var cases = [ // REQUESTS ================================================================== @@ -35,7 +37,6 @@ var cases = [ 'Accept', '*/*', ], - upgrade: false, body: undefined }, { @@ -79,7 +80,6 @@ var cases = [ 'Connection', 'keep-alive', ], - upgrade: false, body: undefined }, { @@ -100,7 +100,6 @@ var cases = [ 'aaaaaaaaaaaaa', '++++++++++', ], - upgrade: false, body: undefined }, { @@ -117,7 +116,6 @@ var cases = [ method: 'GET', url: '/forums/1/topics/2375?page=1#posts-17408', headers: [], - upgrade: false, body: undefined }, { @@ -134,7 +132,6 @@ var cases = [ method: 'GET', url: '/get_no_headers_no_body/world', headers: [], - upgrade: false, body: undefined }, { @@ -155,7 +152,6 @@ var cases = [ 'Accept', '*/*', ], - upgrade: false, body: undefined }, { @@ -177,7 +173,6 @@ var cases = [ 'conTENT-Length', '5', ], - upgrade: false, body: 'HELLO' }, { @@ -205,7 +200,6 @@ var cases = [ 'Content-Length', '5', ], - upgrade: false, body: 'World' }, { @@ -230,7 +224,6 @@ var cases = [ 'Transfer-Encoding', 'chunked', ], - upgrade: false, body: 'all your base are belong to us' }, { @@ -257,7 +250,6 @@ var cases = [ 'Transfer-Encoding', 'chunked', ], - upgrade: false, body: 'hello world' }, { @@ -290,7 +282,6 @@ var cases = [ 'Content-Type', 'text/plain', ], - upgrade: false, body: 'hello world' }, { @@ -317,7 +308,6 @@ var cases = [ 'Transfer-Encoding', 'chunked', ], - upgrade: false, body: 'hello world' }, { @@ -334,7 +324,6 @@ var cases = [ method: 'GET', url: '/with_"stupid"_quotes?foo="bar"', headers: [], - upgrade: false, body: undefined }, { @@ -361,7 +350,6 @@ var cases = [ 'Accept', '*/*', ], - upgrade: false, body: undefined }, { @@ -378,7 +366,6 @@ var cases = [ method: 'GET', url: '/test.cgi?foo=bar?baz', headers: [], - upgrade: false, body: undefined }, { @@ -396,7 +383,6 @@ var cases = [ method: 'GET', url: '/test', headers: [], - upgrade: false, body: undefined }, { @@ -411,7 +397,7 @@ var cases = [ 'Upgrade: WebSocket', 'Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5', 'Origin: http://example.com', - '', '', + '', 'Hot diggity dogg' ].join(CRLF), shouldKeepAlive: true, @@ -436,7 +422,7 @@ var cases = [ 'Origin', 'http://example.com', ], - upgrade: true, + upgrade: 'Hot diggity dogg', body: undefined }, { @@ -446,7 +432,7 @@ var cases = [ 'CONNECT 0-home0.netscape.com:443 HTTP/1.0', 'User-agent: Mozilla/1.1N', 'Proxy-authorization: basic aGVsbG86d29ybGQ=', - '', '', + '', 'some data', 'and yet even more data' ].join(CRLF), @@ -462,7 +448,7 @@ var cases = [ 'Proxy-authorization', 'basic aGVsbG86d29ybGQ=', ], - upgrade: true, + upgrade: 'some data\r\nand yet even more data', body: undefined }, { @@ -479,7 +465,6 @@ var cases = [ method: 'REPORT', url: '/test', headers: [], - upgrade: false, body: undefined }, { @@ -496,7 +481,6 @@ var cases = [ method: 'GET', url: '/', headers: [], - upgrade: false, body: undefined }, { @@ -523,7 +507,6 @@ var cases = [ 'ST', '"ssdp:all"', ], - upgrade: false, body: undefined }, { @@ -564,7 +547,6 @@ var cases = [ 'Connection', 'close', ], - upgrade: false, body: undefined }, { @@ -581,7 +563,6 @@ var cases = [ method: 'GET', url: 'http://example.org?hail=all', headers: [], - upgrade: false, body: undefined }, { @@ -598,7 +579,6 @@ var cases = [ method: 'GET', url: 'http://example.org:1234?hail=all', headers: [], - upgrade: false, body: undefined }, { @@ -615,7 +595,6 @@ var cases = [ method: 'GET', url: 'http://example.org:1234', headers: [], - upgrade: false, body: undefined }, { @@ -646,7 +625,6 @@ var cases = [ 'Content-Length', '10', ], - upgrade: false, body: 'cccccccccc' }, { @@ -670,7 +648,7 @@ var cases = [ 'Proxy-authorization', 'basic aGVsbG86d29ybGQ=', ], - upgrade: true, + upgrade: '', body: undefined }, { @@ -692,7 +670,6 @@ var cases = [ 'Host', 'github.com', ], - upgrade: false, body: undefined }, { @@ -716,7 +693,7 @@ var cases = [ 'Proxy-authorization', 'basic aGVsbG86d29ybGQ=', ], - upgrade: true, + upgrade: '', body: undefined }, { @@ -745,7 +722,6 @@ var cases = [ 'Content-Length', '4', ], - upgrade: false, body: 'q=42' }, { @@ -777,7 +753,6 @@ var cases = [ 'Connection', 'close', ], - upgrade: false, body: 'q=42' }, { @@ -798,7 +773,6 @@ var cases = [ 'Host', 'www.example.com', ], - upgrade: false, body: undefined }, { @@ -819,7 +793,6 @@ var cases = [ 'Host', 'www.example.com', ], - upgrade: false, body: undefined }, { @@ -836,7 +809,6 @@ var cases = [ method: 'GET', url: 'http://a%12:b!&*$@example.org:1234/toto', headers: [], - upgrade: false, body: undefined }, { @@ -852,7 +824,7 @@ var cases = [ 'Upgrade: WebSocket', 'Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5', 'Origin: http://example.com', - '', '', + '', 'Hot diggity dogg' ].join(CRLF), shouldKeepAlive: true, @@ -877,7 +849,7 @@ var cases = [ 'Origin', 'http://example.com', ], - upgrade: true, + upgrade: 'Hot diggity dogg', body: undefined }, { @@ -887,7 +859,7 @@ var cases = [ 'GET /demo HTTP/1.1', 'Connection: keep-alive, upgrade', 'Upgrade: WebSocket', - '', '', + '', 'Hot diggity dogg' ].join(CRLF), shouldKeepAlive: true, @@ -902,7 +874,7 @@ var cases = [ 'Upgrade', 'WebSocket', ], - upgrade: true, + upgrade: 'Hot diggity dogg', body: undefined }, { @@ -913,7 +885,7 @@ var cases = [ 'Connection: keep-alive, ', ' upgrade', 'Upgrade: WebSocket', - '', '', + '', 'Hot diggity dogg' ].join(CRLF), shouldKeepAlive: true, @@ -928,7 +900,7 @@ var cases = [ 'Upgrade', 'WebSocket', ], - upgrade: true, + upgrade: 'Hot diggity dogg', body: undefined }, // RESPONSES ================================================================= @@ -975,7 +947,6 @@ var cases = [ 'Content-Length', '219', ], - upgrade: false, body: '\n301 Moved\n

301 ' + 'Moved

\nThe document has moved\n\n\n ' + '\n \n SOAP-ENV:' @@ -1037,7 +1007,6 @@ var cases = [ statusCode: 404, statusText: 'Not Found', headers: [], - upgrade: false, body: undefined }, { @@ -1054,7 +1023,6 @@ var cases = [ statusCode: 301, statusText: '', headers: [], - upgrade: false, body: undefined }, { @@ -1084,7 +1052,6 @@ var cases = [ 'Transfer-Encoding', 'chunked', ], - upgrade: false, body: 'This is the data in the first chunk\r\n' + 'and this is the second one\r\n' }, @@ -1117,7 +1084,6 @@ var cases = [ 'v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;' + 'dcmt=text/xml;;~cs=o', ], - upgrade: false, body: undefined }, { @@ -1162,7 +1128,6 @@ var cases = [ 'Connection', 'keep-alive', ], - upgrade: false, body: undefined }, { @@ -1215,7 +1180,6 @@ var cases = [ 'Connection', 'close', ], - upgrade: false, body: undefined }, { @@ -1242,7 +1206,6 @@ var cases = [ 'Connection', 'close', ], - upgrade: false, body: undefined }, { @@ -1264,7 +1227,6 @@ var cases = [ 'Content-Type', 'text/plain', ], - upgrade: false, body: 'hello world' }, { @@ -1285,7 +1247,6 @@ var cases = [ 'Connection', 'keep-alive', ], - upgrade: false, body: undefined }, { @@ -1306,7 +1267,6 @@ var cases = [ 'Connection', 'keep-alive', ], - upgrade: false, body: undefined }, { @@ -1323,7 +1283,6 @@ var cases = [ statusCode: 200, statusText: 'OK', headers: [], - upgrade: false, body: undefined }, { @@ -1340,7 +1299,6 @@ var cases = [ statusCode: 204, statusText: 'No content', headers: [], - upgrade: false, body: undefined }, { @@ -1361,11 +1319,10 @@ var cases = [ 'Connection', 'close', ], - upgrade: false, body: undefined }, { - name: 'HTTP/1.1 with chunked endocing and a 200 response', + name: 'HTTP/1.1 with chunked encoding and a 200 response', type: RESPONSE, raw: [ 'HTTP/1.1 200 OK', @@ -1384,7 +1341,6 @@ var cases = [ 'Transfer-Encoding', 'chunked', ], - upgrade: false, body: undefined }, { @@ -1445,7 +1401,6 @@ var cases = [ 'Transfer-Encoding', 'chunked', ], - upgrade: false, body: '\n' }, { @@ -1462,22 +1417,29 @@ var cases = [ statusCode: 200, statusText: '', headers: [], - upgrade: false, body: undefined }, ]; +for (var i = 0; i < cases.length; ++i) { + if (cases[i].type === RESPONSE) { + requestsEnd = i - 1; + break; + } +} + // Prevent EE warnings since we have many test cases which attach `exit` event // handlers process.setMaxListeners(0); +// Test predefined requests/responses cases.forEach(function(testCase) { var parser = new HTTPParser(testCase.type); - var reqEvents = [ 'onHeaders' ]; + var input = new Buffer(testCase.raw, 'binary'); + var reqEvents = ['onHeaders']; var completed = false; - var allHeaders; - var body; + var message = {}; if (testCase.body !== undefined) reqEvents.push('onBody'); @@ -1486,66 +1448,34 @@ cases.forEach(function(testCase) { statusCode, statusText, upgrade, shouldKeepAlive) { assert.strictEqual(reqEvents[0], 'onHeaders', - 'Expected onHeaders to the next event for: ' - + testCase.name); + 'Expected onHeaders to the next event for: ' + + testCase.name); reqEvents.shift(); - _assert(assert.strictEqual, - 'versionMajor', - testCase, - versionMajor, - testCase.httpMajor); - _assert(assert.strictEqual, - 'versionMinor', - testCase, - versionMinor, - testCase.httpMinor); - // Defer checking headers in case there are trailers ... - allHeaders = headers; - if (testCase.type === REQUEST) { - _assert(assert.strictEqual, - 'method', - testCase, - method, - testCase.method); - _assert(assert.strictEqual, - 'url', - testCase, - url, - testCase.url); - } else { - _assert(assert.strictEqual, - 'statusCode', - testCase, - statusCode, - testCase.statusCode); - _assert(assert.strictEqual, - 'statusText', - testCase, - statusText, - testCase.statusText); - } - _assert(assert.strictEqual, - 'upgrade', - testCase, - upgrade, - testCase.upgrade); - _assert(assert.strictEqual, - 'shouldKeepAlive', - testCase, - shouldKeepAlive, - testCase.shouldKeepAlive); + message = { + type: (method === undefined && url === undefined ? RESPONSE : REQUEST), + shouldKeepAlive: shouldKeepAlive, + //msgCompleteOnEOF + httpMajor: versionMajor, + httpMinor: versionMinor, + method: method, + url: url, + headers: headers, + statusCode: statusCode, + statusText: statusText, + upgrade: upgrade + }; } function onBody(data, offset, len) { - if (body === undefined) { + if (message.body === undefined) { assert.strictEqual(reqEvents[0], 'onBody', - 'Expected onBody to be the next event for: ' - + testCase.name); + 'Expected onBody to be the next event for: ' + + testCase.name); reqEvents.shift(); - body = data.toString('binary', offset, offset + len); + message.body = data.toString('binary', offset, offset + len); } else - body += data.toString('binary', offset, offset + len); + message.body += data.toString('binary', offset, offset + len); } function onComplete() { @@ -1553,18 +1483,12 @@ cases.forEach(function(testCase) { 0, 'Missed ' + reqEvents + ' event(s) for: ' + testCase.name); - if (parser.headers.length > 0) - allHeaders = allHeaders.concat(parser.headers); - _assert(assert.deepEqual, - 'headers', - testCase, - allHeaders, - testCase.headers); - _assert(assert.strictEqual, - 'body', - testCase, - body, - testCase.body); + if (parser.headers.length > 0) { + if (message.headers) + message.headers = message.headers.concat(parser.headers); + else + message.headers = parser.headers; + } completed = true; } @@ -1578,8 +1502,9 @@ cases.forEach(function(testCase) { 'Parsing did not complete for: ' + testCase.name); }); + var ret; try { - var ret = parser.execute(new Buffer(testCase.raw, 'binary')); + ret = parser.execute(input); parser.finish(); } catch (ex) { throw new Error('Unexpected error thrown for: ' + testCase.name + ':\n\n' + @@ -1591,11 +1516,654 @@ cases.forEach(function(testCase) { throw new Error('Unexpected error for: ' + testCase.name + ':\n\n' + ret.stack + '\n'); } + if (message.upgrade === false || typeof ret !== 'number') + message.upgrade = undefined; + else + message.upgrade = input.toString('binary', ret); + assertMessageEquals(message, testCase); +}); + +// Test execute() return value +(function() { + var parser = new HTTPParser(REQUEST); + var input = 'GET / HTTP/1.1\r\nheader: value\r\nhdr: value\r\n'; + var ret; + + parser.onHeaders = parser.onBody = parser.onComplete = function() {}; + ret = parser.execute(new Buffer(input)); + assert.strictEqual(ret, Buffer.byteLength(input)); +})(); + +// Test for header overflow +[REQUEST, RESPONSE].forEach(function(type) { + var parser = new HTTPParser(type); + var input = (type === REQUEST ? 'GET / HTTP/1.1\r\n' : 'HTTP/1.0 200 OK\r\n'); + var ret; + + parser.onHeaders = parser.onBody = parser.onComplete = function() {}; + ret = parser.execute(new Buffer(input)); + assert.strictEqual(ret, Buffer.byteLength(input)); + + input = new Buffer('header-key: header-value\r\n'); + for (var i = 0; i < 10000; ++i) { + ret = parser.execute(input); + if (typeof ret !== 'number') { + assert(/Header size limit exceeded/i.test(ret.message)); + return; + } + } + + throw new Error('Error expected but none in header overflow test'); +}); + +// Test for no overflow with long body +[REQUEST, RESPONSE].forEach(function(type) { + [1000, 100000].forEach(function(length) { + var parser = new HTTPParser(type); + var input = format( + '%s\r\nConnection: Keep-Alive\r\nContent-Length: %d\r\n\r\n', + type === REQUEST ? 'POST / HTTP/1.0' : 'HTTP/1.0 200 OK', + length + ); + var input2 = new Buffer('a'); + var ret; + + parser.onHeaders = parser.onBody = parser.onComplete = function() {}; + ret = parser.execute(new Buffer(input)); + assert.strictEqual(ret, Buffer.byteLength(input)); + + for (var i = 0; i < length; ++i) { + ret = parser.execute(input2); + assert.strictEqual(ret, 1); + } + + ret = parser.execute(new Buffer(input)); + assert.strictEqual(ret, Buffer.byteLength(input)); + }); }); -function _assert(assertFn, type, testCase, actual, expected) { - assertFn(actual, - expected, - type + ' mismatch for: ' + testCase.name + '\nActual:\n' + - inspect(actual) + '\nExpected:\n' + inspect(expected) + '\n'); +// Test for content length overflow +['9007199254740991', '9007199254740992', '9007199254740993'].forEach( + function(length, i) { + var parser = new HTTPParser(RESPONSE); + var input = format('HTTP/1.1 200 OK\r\nContent-Length: %s\r\n\r\n', length); + var ret; + + parser.onHeaders = parser.onBody = parser.onComplete = function() {}; + ret = parser.execute(new Buffer(input)); + if (i === 0) + assert.strictEqual(ret, Buffer.byteLength(input)); + else { + assert.strictEqual(typeof ret !== 'number', true); + assert.strictEqual(/Bad Content-Length/i.test(ret.message), true); + } + } +); + +// Test for chunk length overflow +['1fffffffffffff', '20000000000000', '20000000000001'].forEach( + function(length, i) { + var parser = new HTTPParser(RESPONSE); + var input = format('HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n' + + '%s\r\n...', length); + var ret; + + parser.onHeaders = parser.onBody = parser.onComplete = function() {}; + ret = parser.execute(new Buffer(input)); + if (i === 0) + assert.strictEqual(ret, Buffer.byteLength(input)); + else { + assert.strictEqual(typeof ret !== 'number', true); + assert.strictEqual(/Chunk size too big/i.test(ret.message), true); + } + } +); + +// Test pipelined responses +(function() { + var responsesStart = requestsEnd + 1; + for (var i = responsesStart; i < cases.length; ++i) { + if (!cases[i].shouldKeepAlive) + continue; + for (var j = responsesStart; j < cases.length; ++j) { + if (!cases[j].shouldKeepAlive) + continue; + for (var k = responsesStart; k < cases.length; ++k) + testMultiple3(cases[i], cases[j], cases[k]); + } + } +})(); + +// Test response body sizes +[ + getMessageByName('404 no headers no body'), + getMessageByName('200 trailing space on chunked body'), + { + name: 'large chunked message', + type: RESPONSE, + raw: createLargeChunkedMessage(31337, [ + 'HTTP/1.0 200 OK', + 'Transfer-Encoding: chunked', + 'Content-Type: text/plain', + '', '' + ].join(CRLF)), + shouldKeepAlive: false, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 0, + statusCode: 200, + statusText: 'OK', + headers: [ + 'Transfer-Encoding', + 'chunked', + 'Content-Type', + 'text/plain' + ], + bodySize: 31337 * 1024 + } +].forEach(function(expected) { + var parser = new HTTPParser(expected.type); + var expectedBodySize = (expected.bodySize !== undefined + ? expected.bodySize + : (expected.body && expected.body.length) || 0); + var messages = []; + var message = {}; + var ret; + var body; + + parser.onHeaders = function(versionMajor, versionMinor, headers, method, url, + statusCode, statusText, upgrade, + shouldKeepAlive) { + message = { + type: (method === undefined && url === undefined ? RESPONSE : REQUEST), + shouldKeepAlive: shouldKeepAlive, + //msgCompleteOnEOF + httpMajor: versionMajor, + httpMinor: versionMinor, + method: method, + url: url, + headers: headers, + statusCode: statusCode, + statusText: statusText + }; + }; + parser.onBody = function(data, offset, len) { + if (message.bodySize === undefined) { + message.bodySize = len; + body = data.toString('binary', offset, offset + len); + } else { + message.bodySize += len; + body += data.toString('binary', offset, offset + len); + } + }; + parser.onComplete = function() { + messages.push(message); + message = {}; + }; + + var l = expected.raw.length; + var chunk = 4024; + + for (var i = 0; i < l; i += chunk) { + var toread = Math.min(l - i, chunk); + ret = parser.execute( + new Buffer(expected.raw.slice(i, i + toread), 'binary') + ); + assert.strictEqual(ret, toread); + } + assert.strictEqual(parser.finish(), undefined); + + assert.strictEqual(messages.length, 1); + assertMessageEquals(messages[0], expected, ['body', 'upgrade']); + assert.strictEqual(messages[0].bodySize || 0, expectedBodySize); +}); + + +// Perform scan tests on some responses +console.log('response scan 1/2 '); +testScan(getMessageByName('200 trailing space on chunked body'), + getMessageByName('HTTP/1.0 with keep-alive and a 204 status'), + getMessageByName('301 no response phrase')); +console.log('response scan 2/2 '); +testScan(getMessageByName('no merge with empty value'), + getMessageByName('underscore header key'), + { + name: 'ycombinator headers', + type: RESPONSE, + raw: [ + 'HTTP/1.1 200 OK', + 'Content-Type: text/html; charset=utf-8', + 'Connection: close', + '', + 'these headers are from http://news.ycombinator.com/' + ].join(CRLF), + shouldKeepAlive: false, + msgCompleteOnEOF: true, + httpMajor: 1, + httpMinor: 1, + statusCode: 200, + statusText: 'OK', + headers: [ + 'Content-Type', + 'text/html; charset=utf-8', + 'Connection', + 'close', + ], + body: 'these headers are from http://news.ycombinator.com/' + }); +console.log('responses okay'); + + + + +// Test malformed HTTP version in request +(function() { + var parser = new HTTPParser(REQUEST); + var input = 'GET / HTP/1.1\r\n\r\n'; + var ret; + + parser.onHeaders = parser.onBody = parser.onComplete = function() {}; + ret = parser.execute(new Buffer(input)); + assert.strictEqual(typeof ret !== 'number', true); + assert.strictEqual(/Malformed request line/i.test(ret.message), true); +})(); + +// Test well-formed but incomplete request +(function() { + var parser = new HTTPParser(REQUEST); + var input = 'GET / HTTP/1.1\r\nContent-Type: text/plain\r\n' + + 'Content-Length: 6\r\n\r\nfooba'; + var ret; + + parser.onHeaders = parser.onBody = parser.onComplete = function() {}; + ret = parser.execute(new Buffer(input)); + assert.strictEqual(ret, input.length); +})(); + +// Test illegal header field name line folding in request +(function() { + var parser = new HTTPParser(REQUEST); + var input = 'GET / HTTP/1.1\r\nname\r\n : value\r\n\r\n'; + var ret; + + parser.onHeaders = parser.onBody = parser.onComplete = function() {}; + ret = parser.execute(new Buffer(input)); + assert.strictEqual(typeof ret !== 'number', true); + assert.strictEqual(/Malformed header line/i.test(ret.message), true); +})(); + +// Test large SSL certificate header value in request +(function() { + var parser = new HTTPParser(REQUEST); + var input = + 'GET / HTTP/1.1\r\n' + + 'X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n' + + '\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n' + + '\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n' + + '\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n' + + '\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n' + + '\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n' + + '\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n' + + '\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n' + + '\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n' + + '\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n' + + '\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n' + + '\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n' + + '\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n' + + '\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n' + + '\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n' + + '\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n' + + '\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n' + + '\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n' + + '\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n' + + '\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n' + + '\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n' + + '\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBD' + + 'AWLmh0\r\n' + + '\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n' + + '\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n' + + '\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n' + + '\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n' + + '\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n' + + '\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n' + + '\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n' + + '\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n' + + '\tRA==\r\n' + + '\t-----END CERTIFICATE-----\r\n' + + '\r\n'; + var ret; + + parser.onHeaders = parser.onBody = parser.onComplete = function() {}; + ret = parser.execute(new Buffer(input)); + assert.strictEqual(ret, input.length); +})(); + + +// Test pipelined requests +(function() { + for (var i = 0; i <= requestsEnd; ++i) { + if (!cases[i].shouldKeepAlive) + continue; + for (var j = 0; j <= requestsEnd; ++j) { + if (!cases[j].shouldKeepAlive) + continue; + for (var k = 0; k <= requestsEnd; ++k) + testMultiple3(cases[i], cases[j], cases[k]); + } + } +})(); + + +// Perform scan tests on some requests +console.log('request scan 1/4 '); +testScan(getMessageByName('get no headers no body'), + getMessageByName('get one header no body'), + getMessageByName('get no headers no body')); +console.log('request scan 2/4 '); +testScan(getMessageByName( + 'post - chunked body: all your base are belong to us' + ), + getMessageByName('post identity body world'), + getMessageByName('get funky content length body hello')); +console.log('request scan 3/4 '); +testScan(getMessageByName('two chunks ; triple zero ending'), + getMessageByName('chunked with trailing headers'), + getMessageByName('chunked with chunk extensions')); +console.log('request scan 4/4 '); +testScan(getMessageByName('query url with question mark'), + getMessageByName('newline prefix get'), + getMessageByName('connect request')); +console.log('requests okay'); + + + + +// HELPER FUNCTIONS ============================================================ + +// SCAN through every possible breaking to make sure the parser can handle +// getting the content in any chunks that might come from the socket +function testScan(case1, case2, case3) { + var messageCount = countParsedMessages(case1, case2, case3); + var total = case1.raw + case2.raw + case3.raw; + var totallen = total.length; + var totalops = (totallen - 1) * (totallen - 2) / 2; + var messages = []; + var message = {}; + var ops = 0; + var nb = 0; + var hasUpgrade; + var ret; + + function onHeaders(versionMajor, versionMinor, headers, method, url, + statusCode, statusText, upgrade, shouldKeepAlive) { + message = { + type: (method === undefined && url === undefined ? RESPONSE : REQUEST), + shouldKeepAlive: shouldKeepAlive, + //msgCompleteOnEOF + httpMajor: versionMajor, + httpMinor: versionMinor, + method: method, + url: url, + headers: headers, + statusCode: statusCode, + statusText: statusText, + upgrade: upgrade + }; + } + function onBody(data, offset, len) { + if (!message.body) + message.body = data.toString('binary', offset, offset + len); + else + message.body += data.toString('binary', offset, offset + len); + } + function onComplete() { + if (parser.headers.length > 0) { + if (message.headers) + message.headers = message.headers.concat(parser.headers); + else + message.headers = parser.headers; + } + messages.push(message); + message = {}; + } + + for (var j = 2; j < totallen; ++j) { + for (var i = 1; i < j; ++i) { + if (ops % 1000 === 0) { + var value = Math.floor(100 * ops / totalops); + if (value < 10) + value = ' ' + value; + else if (value < 100) + value = ' ' + value; + else + value = '' + value; + console.log('\b\b\b\b%s%', value); + } + ++ops; + + var parser = new HTTPParser(case1.type); + parser.onHeaders = onHeaders; + parser.onBody = onBody; + parser.onComplete = onComplete; + + messages = []; + hasUpgrade = false; + nb = 0; + + ret = parser.execute(new Buffer(total.slice(0, i), 'binary')); + assert.strictEqual(typeof ret === 'number', true); + nb += ret; + + for (var k = 0; k < messages.length; ++k) { + if (messages[k].upgrade === true) + hasUpgrade = true; + else + delete messages[k].upgrade; + } + + if (!hasUpgrade) { + assert.strictEqual(nb, i); + + ret = parser.execute(new Buffer(total.slice(i, j), 'binary')); + assert.strictEqual(typeof ret === 'number', true); + nb += ret; + + for (var k = 0; k < messages.length; ++k) { + if (messages[k].upgrade === true) + hasUpgrade = true; + else + delete messages[k].upgrade; + } + + if (!hasUpgrade) { + assert.strictEqual(nb, i + (j - i)); + + ret = parser.execute(new Buffer(total.slice(j), 'binary')); + assert.strictEqual(typeof ret === 'number', true); + nb += ret; + + for (var k = 0; k < messages.length; ++k) { + if (messages[k].upgrade === true) + hasUpgrade = true; + else + delete messages[k].upgrade; + } + + if (!hasUpgrade) + assert.strictEqual(nb, i + (j - i) + (totallen - j)); + } + } + + assert.strictEqual(parser.finish(), undefined); + assert.strictEqual(messages.length, messageCount); + + for (var k = 0; k < messages.length; ++k) { + if (messages[k].upgrade !== true) + delete messages[k].upgrade; + } + + if (hasUpgrade) { + var lastMessage = messages.slice(-1)[0]; + upgradeMessageFix(total, nb, lastMessage, case1, case2, case3); + } + + assertMessageEquals(messages[0], case1); + if (messages.length > 1) + assertMessageEquals(messages[1], case2); + if (messages.length > 2) + assertMessageEquals(messages[2], case3); + } + } + console.log('\b\b\b\b100%'); +} + +function testMultiple3(case1, case2, case3) { + var messageCount = countParsedMessages(case1, case2, case3); + var total = case1.raw + case2.raw + case3.raw; + var parser = new HTTPParser(case1.type); + var messages = []; + var message = {}; + var ret; + + parser.onHeaders = function(versionMajor, versionMinor, headers, method, url, + statusCode, statusText, upgrade, + shouldKeepAlive) { + message = { + type: (method === undefined && url === undefined ? RESPONSE : REQUEST), + shouldKeepAlive: shouldKeepAlive, + //msgCompleteOnEOF + httpMajor: versionMajor, + httpMinor: versionMinor, + method: method, + url: url, + headers: headers, + statusCode: statusCode, + statusText: statusText, + upgrade: upgrade + }; + }; + parser.onBody = function(data, offset, len) { + if (!message.body) + message.body = data.toString('binary', offset, offset + len); + else + message.body += data.toString('binary', offset, offset + len); + }; + parser.onComplete = function() { + if (parser.headers.length > 0) { + if (message.headers) + message.headers = message.headers.concat(parser.headers); + else + message.headers = parser.headers; + } + messages.push(message); + message = {}; + }; + + ret = parser.execute(new Buffer(total, 'binary')); + + assert.strictEqual(parser.finish(), undefined); + assert.strictEqual(messages.length, messageCount); + + var hasUpgrade = false; + for (var i = 0; i < messages.length; ++i) { + if (messages[i].upgrade === true) + hasUpgrade = true; + else + delete messages[i].upgrade; + } + + if (hasUpgrade) { + var lastMessage = messages.slice(-1)[0]; + upgradeMessageFix(total, ret, lastMessage, case1, case2, case3); + } else + assert.strictEqual(ret, total.length); + + assertMessageEquals(messages[0], case1); + if (messages.length > 1) + assertMessageEquals(messages[1], case2); + if (messages.length > 2) + assertMessageEquals(messages[2], case3); +} + +function upgradeMessageFix(body, ret, actualLast) { + var offset = 0; + + for (var i = 3; i < arguments.length; ++i) { + var caseMsg = arguments[i]; + + offset += caseMsg.raw.length; + + if (caseMsg.upgrade !== undefined) { + offset -= caseMsg.upgrade.length; + + // Check the portion of the response after its specified upgrade + assert.strictEqual(body.slice(offset), body.slice(ret)); + + // Fix up the response so that assertMessageEquals() will verify the + // upgrade correctly + actualLast.upgrade = body.slice(ret, ret + caseMsg.upgrade.length); + return; + } + } + + throw new Error('Expected a message with an upgrade'); +} + +function countParsedMessages() { + for (var i = 0; i < arguments.length; ++i) { + if (arguments[i].upgrade) { + return i + 1; + } + } + return arguments.length; +} + +function createLargeChunkedMessage(bodySizeKB, rawHeaders) { + var wrote = 0; + var headerslen = rawHeaders.length; + var bufsize = headerslen + (5 + 1024 + 2) * bodySizeKB + 5; + var buf = new Buffer(bufsize); + + buf.write(rawHeaders, wrote, headerslen, 'binary'); + wrote += headerslen; + + for (var i = 0; i < bodySizeKB; ++i) { + // Write 1KB chunk into the body. + buf.write('400\r\n', wrote, 5); + wrote += 5; + buf.fill('C', wrote, wrote + 1024); + wrote += 1024; + buf.write('\r\n', wrote, 2); + wrote += 2; + } + + buf.write('0\r\n\r\n', wrote, 5); + wrote += 5; + assert.strictEqual(wrote, bufsize); + + return buf.toString('binary'); +} + +function getMessageByName(name) { + var lowered = name.toLowerCase(); + for (var i = 0; i < cases.length; ++i) { + if (cases[i].name.toLowerCase() === lowered) + return cases[i]; + } + throw new Error('Predefined HTTP message not found for: ' + name); +} + +function assertMessageEquals(actual, expected, except) { + ['type', 'httpMajor', 'httpMinor', 'method', 'url', 'statusCode', + 'statusText', 'shouldKeepAlive', 'headers', 'upgrade', 'body' + ].filter(function(p) { + return (except === undefined || except.indexOf(p) === -1); + }).forEach(function(type) { + var assertFn = (type === 'headers' ? assert.deepEqual : assert.strictEqual); + assertFn(actual[type], + expected[type], + type + ' mismatch for: ' + expected.name + '\nActual:\n' + + inspect(actual[type]) + '\nExpected:\n' + + inspect(expected[type]) + '\n'); + }); } From 6b8afe5a32b46518661ec405a7a9e6eb221e13e2 Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 17 Apr 2015 18:35:22 -0400 Subject: [PATCH 44/67] http: remove unused variables --- lib/_http_parser.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index adb013780afb0f..2e01b9766b051c 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -233,17 +233,6 @@ var STATE_BODY_CHUNKED_SIZE = 5; var STATE_BODY_CHUNKED_BYTES = 6; var STATE_BODY_CHUNKED_BYTES_CRLF = 7; var STATE_COMPLETE = 8; -var STATE_NAMES = [ - 'STATE_REQ_LINE', - 'STATE_STATUS_LINE', - 'STATE_HEADER', - 'STATE_BODY_LITERAL', - 'STATE_BODY_EOF', - 'STATE_BODY_CHUNKED_SIZE', - 'STATE_BODY_CHUNKED_BYTES', - 'STATE_BODY_CHUNKED_BYTES_CRLF', - 'STATE_COMPLETE' -]; var FLAG_CHUNKED = 1 << 0; var FLAG_CONNECTION_KEEP_ALIVE = 1 << 1; @@ -825,7 +814,7 @@ HTTPParser.prototype._needsEOF = function() { var REQUEST = HTTPParser.REQUEST = 0; -var RESPONSE = HTTPParser.RESPONSE = 1; +var HTTPParser.RESPONSE = 1; module.exports = HTTPParser; function indexOfCRLF(buf, buflen, offset) { From 86b6bda615136452be67d913f48f0dc160eafba2 Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 17 Apr 2015 19:24:43 -0400 Subject: [PATCH 45/67] http: move keepalive boolean to flag This improves performance some by not having to (re)set another instance property. --- lib/_http_parser.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 2e01b9766b051c..fd23c6089a7e83 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -241,6 +241,7 @@ var FLAG_CONNECTION_UPGRADE = 1 << 3; var FLAG_TRAILING = 1 << 4; var FLAG_UPGRADE = 1 << 5; var FLAG_SKIPBODY = 1 << 6; +var FLAG_SHOULD_KEEP_ALIVE = 1 << 7; var FLAG_ANY_UPGRADE = FLAG_UPGRADE | FLAG_CONNECTION_UPGRADE; function HTTPParser(type) { @@ -266,7 +267,6 @@ HTTPParser.prototype.reinitialize = function(type) { this._err = undefined; this._flags = 0; this._contentLen = undefined; - this._keepalive = false; this._nbytes = 0; this._nhdrbytes = 0; this._nhdrpairs = 0; @@ -442,8 +442,8 @@ HTTPParser.prototype._headersEnd = function() { var contentLen = this._contentLen; var httpMajor = this.httpMajor; var httpMinor = this.httpMinor; - var keepalive = this._keepalive = this._shouldKeepAlive(httpMajor, httpMinor, - flags); + var statusCode = this.statusCode; + var keepalive = this._shouldKeepAlive(httpMajor, httpMinor, flags, type, statusCode); var ret; this._buf = ''; @@ -456,6 +456,8 @@ HTTPParser.prototype._headersEnd = function() { } else if (contentLen !== undefined) { this._state = STATE_BODY_LITERAL; this.execute = this._executeBodyLiteral; + if (keepalive) + this._flags |= FLAG_SHOULD_KEEP_ALIVE; } else { this._state = STATE_BODY_EOF; this.execute = this._executeBodyEOF; @@ -747,7 +749,7 @@ HTTPParser.prototype._executeBodyLiteral = function(data) { if (len >= nbytes) { this.onBody(data, 0, nbytes); this.onComplete && this.onComplete(); - if (this._keepalive) { + if ((this._flags & FLAG_SHOULD_KEEP_ALIVE) > 0) { this.reinitialize(this.type); if (len > nbytes) { ret = this.execute(data.slice(nbytes)); From cfb7f3e4032114e1e2ded8b5f04dc20f365bf1ab Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 17 Apr 2015 19:26:31 -0400 Subject: [PATCH 46/67] http: avoid parsing '1'/'0' The ternary expression is faster, but may not be noticeable. --- lib/_http_parser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index fd23c6089a7e83..fbe8f01975a7f9 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -409,7 +409,7 @@ HTTPParser.prototype._processHdrLine = function(line) { this.httpMinor = 9; this._headersEnd(); } else { - this.httpMinor = parseInt(minor, 10); + this.httpMinor = (minor === '1' ? 1 : 0); this._state = STATE_HEADER; } break; @@ -424,7 +424,7 @@ HTTPParser.prototype._processHdrLine = function(line) { // m[1]: HTTP minor version // m[2]: HTTP status code // m[3]: Reason text - this.httpMinor = parseInt(m[1], 10); + this.httpMinor = (m[1] === '1' ? 1 : 0); this.statusCode = parseInt(m[2], 10); this.statusText = m[3] || ''; this._state = STATE_HEADER; From ae19bae2fde4ec4398df42e97d73783276b305b5 Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 17 Apr 2015 19:30:38 -0400 Subject: [PATCH 47/67] http: lint and style changes --- lib/_http_parser.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index fbe8f01975a7f9..218a0c9006814a 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -259,7 +259,7 @@ function HTTPParser(type) { HTTPParser.prototype.reinitialize = function(type) { this.execute = this._executeStartLine; this.type = type; - if (type === HTTPParser.REQUEST) + if (type === REQUEST) this._state = STATE_REQ_LINE; else this._state = STATE_STATUS_LINE; @@ -397,13 +397,12 @@ HTTPParser.prototype._processHdrLine = function(line) { // m[1]: HTTP method // m[2]: request target // m[3]: HTTP minor version - var method = m[1]; - var minor = m[3]; - this.method = method; + this.method = m[1]; this.url = m[2]; + var minor = m[3]; if (minor === undefined) { // HTTP/0.9 ugh... - if (method !== 'GET') + if (m[1] !== 'GET') return this._setError('Malformed request line'); this.httpMajor = 0; this.httpMinor = 9; @@ -443,7 +442,8 @@ HTTPParser.prototype._headersEnd = function() { var httpMajor = this.httpMajor; var httpMinor = this.httpMinor; var statusCode = this.statusCode; - var keepalive = this._shouldKeepAlive(httpMajor, httpMinor, flags, type, statusCode); + var keepalive = this._shouldKeepAlive(httpMajor, httpMinor, flags, type, + statusCode); var ret; this._buf = ''; @@ -480,7 +480,7 @@ HTTPParser.prototype._headersEnd = function() { if (headerslen > 0) headers[headerslen - 1] = trim(headers[headerslen - 1]); ret = this.onHeaders(httpMajor, httpMinor, headers, method, - this.url, this.statusCode, this.statusText, upgrade, + this.url, statusCode, this.statusText, upgrade, keepalive); if (ret === true) flags = (this._flags |= FLAG_SKIPBODY); @@ -752,7 +752,7 @@ HTTPParser.prototype._executeBodyLiteral = function(data) { if ((this._flags & FLAG_SHOULD_KEEP_ALIVE) > 0) { this.reinitialize(this.type); if (len > nbytes) { - ret = this.execute(data.slice(nbytes)); + var ret = this.execute(data.slice(nbytes)); if (typeof ret !== 'number') return ret; return nbytes + ret; @@ -816,7 +816,7 @@ HTTPParser.prototype._needsEOF = function() { var REQUEST = HTTPParser.REQUEST = 0; -var HTTPParser.RESPONSE = 1; +HTTPParser.RESPONSE = 1; module.exports = HTTPParser; function indexOfCRLF(buf, buflen, offset) { From 6879858eefb406b9d4235dd5a22ff0a24152a66a Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 17 Apr 2015 19:31:21 -0400 Subject: [PATCH 48/67] http: reduce property lookups --- lib/_http_parser.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 218a0c9006814a..3be97427735968 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -494,7 +494,8 @@ HTTPParser.prototype._headersEnd = function() { (flags & FLAG_SKIPBODY) > 0 || ((flags & FLAG_CHUNKED) === 0 && contentLen === undefined && - !this._needsEOF())) { + !this._needsEOF(flags, type, statusCode) + )) { this.onComplete && this.onComplete(); this.reinitialize(type); } @@ -781,7 +782,8 @@ HTTPParser.prototype._executeError = function(data) { return this._err; }; HTTPParser.prototype.execute = HTTPParser.prototype._executeStartLine; -HTTPParser.prototype._shouldKeepAlive = function(httpMajor, httpMinor, flags) { +HTTPParser.prototype._shouldKeepAlive = function(httpMajor, httpMinor, flags, + type, status) { if (httpMajor > 0 && httpMinor > 0) { if ((flags & FLAG_CONNECTION_CLOSE) > 0) return false; @@ -789,20 +791,17 @@ HTTPParser.prototype._shouldKeepAlive = function(httpMajor, httpMinor, flags) { if ((flags & FLAG_CONNECTION_KEEP_ALIVE) === 0) return false; } - return !this._needsEOF(); + return !this._needsEOF(flags, type, status); }; -HTTPParser.prototype._needsEOF = function() { - if (this.type === HTTPParser.REQUEST) +HTTPParser.prototype._needsEOF = function(flags, type, status) { + if (type === REQUEST) return false; // See RFC 2616 section 4.4 - var status = this.statusCode; - var flags = this._flags; - if ((status !== undefined && - (status === 204 || // No Content - status === 304 || // Not Modified - parseInt(status / 100, 10) === 1)) || // 1xx e.g. Continue - (flags & FLAG_SKIPBODY) > 0) { // response to a HEAD request + if (status === 204 || // No Content + status === 304 || // Not Modified + parseInt(status / 100, 10) === 1 || // 1xx e.g. Continue + (flags & FLAG_SKIPBODY) > 0) { // response to a HEAD request return false; } From f6c134fcbf34e6371c009267048dc9b8469674e9 Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 17 Apr 2015 19:50:47 -0400 Subject: [PATCH 49/67] build: another fixup after rebase --- configure | 1 - 1 file changed, 1 deletion(-) diff --git a/configure b/configure index 8a50e2e59d3ca3..32b02170221bbb 100755 --- a/configure +++ b/configure @@ -93,7 +93,6 @@ parser.add_option('--openssl-fips', dest='openssl_fips', help='Build OpenSSL using FIPS canister .o file in supplied folder') - help='alternative lib name to link to [default: %default]') shared_optgroup.add_option('--shared-libuv', action='store_true', dest='shared_libuv', From f5197aed2a0b10e83b81c72961e9ae4ff8f5fa97 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sat, 18 Apr 2015 15:06:09 -0400 Subject: [PATCH 50/67] http: improve multi header value matching --- lib/_http_parser.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 3be97427735968..e0f4a719138cd5 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -75,13 +75,13 @@ var UNHEX = { // recommendation for status lines var MAX_HEADER_BYTES = 80 * 1024; -var RE_CLOSE = /close/i; -var RE_KEEPALIVE = /keep\-alive/i; -var RE_UPGRADE = /upgrade/i; -var RE_CHUNKED = /chunked/i; +var RE_CONN_CLOSE = /(?:^|[\t ,]+)close(?:$|[\t ,]+)/i; +var RE_CONN_KEEPALIVE = /(?:^|[\t ,]+)keep\-alive(?:$|[\t ,]+)/i; +var RE_CONN_UPGRADE = /(?:^|[\t ,]+)upgrade(?:$|[\t ,]+)/i; +var RE_TE_CHUNKED = /(?:^|[\t ,]+)chunked(?:$|[\t ,]+)/i; var CC_CONNECT = 'connect'.split('').map(getFirstCharCode); var CC_CONNECTION = 'connection'.split('').map(getFirstCharCode); -var CC_XFERENC = 'transfer-encoding'.split('').map(getFirstCharCode); +var CC_TE = 'transfer-encoding'.split('').map(getFirstCharCode); var CC_UPGRADE = 'upgrade'.split('').map(getFirstCharCode); var CC_CONTLEN = 'content-length'.split('').map(getFirstCharCode); @@ -335,14 +335,14 @@ HTTPParser.prototype._processHdrLine = function(line) { fieldValue = headers[headerslen - 1] + ' ' + extra; // Need to re-check value since matched values may now exist ... if (equalsLower(fieldName, CC_CONNECTION)) { - if (fieldValue.search(RE_CLOSE) > -1) + if (fieldValue.search(RE_CONN_CLOSE) > -1) this._flags |= FLAG_CONNECTION_CLOSE; - if (fieldValue.search(RE_KEEPALIVE) > -1) + if (fieldValue.search(RE_CONN_KEEPALIVE) > -1) this._flags |= FLAG_CONNECTION_KEEP_ALIVE; - if (fieldValue.search(RE_UPGRADE) > -1) + if (fieldValue.search(RE_CONN_UPGRADE) > -1) this._flags |= FLAG_CONNECTION_UPGRADE; - } else if (equalsLower(fieldName, CC_XFERENC)) { - if (fieldValue.search(RE_CHUNKED) > -1) + } else if (equalsLower(fieldName, CC_TE)) { + if (fieldValue.search(RE_TE_CHUNKED) > -1) this._flags |= FLAG_CHUNKED; } else if (equalsLower(fieldName, CC_UPGRADE)) { this._flags |= FLAG_UPGRADE; @@ -364,14 +364,14 @@ HTTPParser.prototype._processHdrLine = function(line) { fieldName = m[1]; fieldValue = m[2]; if (equalsLower(fieldName, CC_CONNECTION)) { - if (fieldValue.search(RE_CLOSE) > -1) + if (fieldValue.search(RE_CONN_CLOSE) > -1) this._flags |= FLAG_CONNECTION_CLOSE; - if (fieldValue.search(RE_KEEPALIVE) > -1) + if (fieldValue.search(RE_CONN_KEEPALIVE) > -1) this._flags |= FLAG_CONNECTION_KEEP_ALIVE; - if (fieldValue.search(RE_UPGRADE) > -1) + if (fieldValue.search(RE_CONN_UPGRADE) > -1) this._flags |= FLAG_CONNECTION_UPGRADE; - } else if (equalsLower(fieldName, CC_XFERENC)) { - if (fieldValue.search(RE_CHUNKED) > -1) + } else if (equalsLower(fieldName, CC_TE)) { + if (fieldValue.search(RE_TE_CHUNKED) > -1) this._flags |= FLAG_CHUNKED; } else if (equalsLower(fieldName, CC_UPGRADE)) { this._flags |= FLAG_UPGRADE; From ede7529fb735456bbb17cb72f96c6dc93774ad66 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sat, 18 Apr 2015 16:56:25 -0400 Subject: [PATCH 51/67] http: use null instead of undefined --- lib/_http_parser.js | 32 ++--- test/parallel/test-http-parser-durability.js | 122 ++++++++++++++++++- 2 files changed, 134 insertions(+), 20 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index e0f4a719138cd5..b723cfed74a1f6 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -245,14 +245,14 @@ var FLAG_SHOULD_KEEP_ALIVE = 1 << 7; var FLAG_ANY_UPGRADE = FLAG_UPGRADE | FLAG_CONNECTION_UPGRADE; function HTTPParser(type) { - this.onHeaders = undefined; - this.onBody = undefined; - this.onComplete = undefined; + this.onHeaders = null; + this.onBody = null; + this.onComplete = null; // extra stuff tagged onto parser object by core modules/files - this.onIncoming = undefined; - this.incoming = undefined; - this.socket = undefined; + this.onIncoming = null; + this.incoming = null; + this.socket = null; this.reinitialize(type); } @@ -264,9 +264,9 @@ HTTPParser.prototype.reinitialize = function(type) { else this._state = STATE_STATUS_LINE; - this._err = undefined; + this._err = null; this._flags = 0; - this._contentLen = undefined; + this._contentLen = null; this._nbytes = 0; this._nhdrbytes = 0; this._nhdrpairs = 0; @@ -276,16 +276,16 @@ HTTPParser.prototype.reinitialize = function(type) { // common properties this.headers = []; this.httpMajor = 1; - this.httpMinor = undefined; + this.httpMinor = null; this.maxHeaderPairs = 2000; // request properties - this.method = undefined; - this.url = undefined; + this.method = null; + this.url = null; // response properties - this.statusCode = undefined; - this.statusText = undefined; + this.statusCode = null; + this.statusText = null; }; HTTPParser.prototype.finish = function() { var state = this._state; @@ -453,7 +453,7 @@ HTTPParser.prototype._headersEnd = function() { if ((flags & FLAG_CHUNKED) > 0) { this._state = STATE_BODY_CHUNKED_SIZE; this.execute = this._executeBodyChunked; - } else if (contentLen !== undefined) { + } else if (contentLen !== null) { this._state = STATE_BODY_LITERAL; this.execute = this._executeBodyLiteral; if (keepalive) @@ -493,7 +493,7 @@ HTTPParser.prototype._headersEnd = function() { } else if (contentLen === 0 || (flags & FLAG_SKIPBODY) > 0 || ((flags & FLAG_CHUNKED) === 0 && - contentLen === undefined && + contentLen === null && !this._needsEOF(flags, type, statusCode) )) { this.onComplete && this.onComplete(); @@ -805,7 +805,7 @@ HTTPParser.prototype._needsEOF = function(flags, type, status) { return false; } - if ((flags & FLAG_CHUNKED) > 0 || this._contentLen !== undefined) + if ((flags & FLAG_CHUNKED) > 0 || this._contentLen !== null) return false; return true; diff --git a/test/parallel/test-http-parser-durability.js b/test/parallel/test-http-parser-durability.js index 99aadb5b760da1..ac7614e583d251 100644 --- a/test/parallel/test-http-parser-durability.js +++ b/test/parallel/test-http-parser-durability.js @@ -28,6 +28,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: '/test', + statusCode: null, + statusText: null, headers: [ 'User-Agent', 'curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 ' @@ -61,6 +63,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: '/favicon.ico', + statusCode: null, + statusText: null, headers: [ 'Host', '0.0.0.0=5000', @@ -96,6 +100,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: '/repeater', + statusCode: null, + statusText: null, headers: [ 'aaaaaaaaaaaaa', '++++++++++', @@ -115,6 +121,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: '/forums/1/topics/2375?page=1#posts-17408', + statusCode: null, + statusText: null, headers: [], body: undefined }, @@ -131,6 +139,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: '/get_no_headers_no_body/world', + statusCode: null, + statusText: null, headers: [], body: undefined }, @@ -148,6 +158,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: '/get_one_header_no_body', + statusCode: null, + statusText: null, headers: [ 'Accept', '*/*', @@ -169,6 +181,8 @@ var cases = [ httpMinor: 0, method: 'GET', url: '/get_funky_content_length_body_hello', + statusCode: null, + statusText: null, headers: [ 'conTENT-Length', '5', @@ -192,6 +206,8 @@ var cases = [ httpMinor: 1, method: 'POST', url: '/post_identity_body_world?q=search#hey', + statusCode: null, + statusText: null, headers: [ 'Accept', '*/*', @@ -220,6 +236,8 @@ var cases = [ httpMinor: 1, method: 'POST', url: '/post_chunked_all_your_base', + statusCode: null, + statusText: null, headers: [ 'Transfer-Encoding', 'chunked', @@ -246,6 +264,8 @@ var cases = [ httpMinor: 1, method: 'POST', url: '/two_chunks_mult_zero_end', + statusCode: null, + statusText: null, headers: [ 'Transfer-Encoding', 'chunked', @@ -274,6 +294,8 @@ var cases = [ httpMinor: 1, method: 'POST', url: '/chunked_w_trailing_headers', + statusCode: null, + statusText: null, headers: [ 'Transfer-Encoding', 'chunked', @@ -304,6 +326,8 @@ var cases = [ httpMinor: 1, method: 'POST', url: '/chunked_w_extensions', + statusCode: null, + statusText: null, headers: [ 'Transfer-Encoding', 'chunked', @@ -323,6 +347,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: '/with_"stupid"_quotes?foo="bar"', + statusCode: null, + statusText: null, headers: [], body: undefined }, @@ -342,6 +368,8 @@ var cases = [ httpMinor: 0, method: 'GET', url: '/test', + statusCode: null, + statusText: null, headers: [ 'Host', '0.0.0.0:5000', @@ -365,6 +393,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: '/test.cgi?foo=bar?baz', + statusCode: null, + statusText: null, headers: [], body: undefined }, @@ -382,6 +412,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: '/test', + statusCode: null, + statusText: null, headers: [], body: undefined }, @@ -406,6 +438,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: '/demo', + statusCode: null, + statusText: null, headers: [ 'Host', 'example.com', @@ -442,6 +476,8 @@ var cases = [ httpMinor: 0, method: 'CONNECT', url: '0-home0.netscape.com:443', + statusCode: null, + statusText: null, headers: [ 'User-agent', 'Mozilla/1.1N', @@ -464,6 +500,8 @@ var cases = [ httpMinor: 1, method: 'REPORT', url: '/test', + statusCode: null, + statusText: null, headers: [], body: undefined }, @@ -480,6 +518,8 @@ var cases = [ httpMinor: 9, method: 'GET', url: '/', + statusCode: null, + statusText: null, headers: [], body: undefined }, @@ -499,6 +539,8 @@ var cases = [ httpMinor: 1, method: 'M-SEARCH', url: '*', + statusCode: null, + statusText: null, headers: [ 'HOST', '239.255.255.250:1900', @@ -535,6 +577,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: '/', + statusCode: null, + statusText: null, headers: [ 'Line1', 'abc def ghi jkl mno qrs', @@ -562,6 +606,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: 'http://example.org?hail=all', + statusCode: null, + statusText: null, headers: [], body: undefined }, @@ -578,6 +624,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: 'http://example.org:1234?hail=all', + statusCode: null, + statusText: null, headers: [], body: undefined }, @@ -594,6 +642,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: 'http://example.org:1234', + statusCode: null, + statusText: null, headers: [], body: undefined }, @@ -615,6 +665,8 @@ var cases = [ httpMinor: 1, method: 'PATCH', url: '/file.txt', + statusCode: null, + statusText: null, headers: [ 'Host', 'www.example.com', @@ -642,6 +694,8 @@ var cases = [ httpMinor: 0, method: 'CONNECT', url: 'HOME0.NETSCAPE.COM:443', + statusCode: null, + statusText: null, headers: [ 'User-agent', 'Mozilla/1.1N', @@ -666,6 +720,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: new Buffer('/δ¶/δt/pope?q=1#narf', 'utf8').toString('binary'), + statusCode: null, + statusText: null, headers: [ 'Host', 'github.com', @@ -687,6 +743,8 @@ var cases = [ httpMinor: 0, method: 'CONNECT', url: 'home_0.netscape.com:443', + statusCode: null, + statusText: null, headers: [ 'User-agent', 'Mozilla/1.1N', @@ -714,6 +772,8 @@ var cases = [ httpMinor: 1, method: 'POST', url: '/', + statusCode: null, + statusText: null, headers: [ 'Host', 'www.example.com', @@ -743,6 +803,8 @@ var cases = [ httpMinor: 1, method: 'POST', url: '/', + statusCode: null, + statusText: null, headers: [ 'Host', 'www.example.com', @@ -769,6 +831,8 @@ var cases = [ httpMinor: 1, method: 'PURGE', url: '/file.txt', + statusCode: null, + statusText: null, headers: [ 'Host', 'www.example.com', @@ -789,6 +853,8 @@ var cases = [ httpMinor: 1, method: 'SEARCH', url: '/', + statusCode: null, + statusText: null, headers: [ 'Host', 'www.example.com', @@ -808,6 +874,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: 'http://a%12:b!&*$@example.org:1234/toto', + statusCode: null, + statusText: null, headers: [], body: undefined }, @@ -833,6 +901,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: '/demo', + statusCode: null, + statusText: null, headers: [ 'Host', 'example.com', @@ -868,6 +938,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: '/demo', + statusCode: null, + statusText: null, headers: [ 'Connection', 'keep-alive, upgrade', @@ -894,6 +966,8 @@ var cases = [ httpMinor: 1, method: 'GET', url: '/demo', + statusCode: null, + statusText: null, headers: [ 'Connection', 'keep-alive, upgrade', @@ -927,6 +1001,8 @@ var cases = [ msgCompleteOnEOF: false, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 301, statusText: 'Moved Permanently', headers: [ @@ -973,6 +1049,8 @@ var cases = [ msgCompleteOnEOF: true, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 200, statusText: 'OK', headers: [ @@ -1004,6 +1082,8 @@ var cases = [ msgCompleteOnEOF: true, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 404, statusText: 'Not Found', headers: [], @@ -1020,6 +1100,8 @@ var cases = [ msgCompleteOnEOF: true, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 301, statusText: '', headers: [], @@ -1044,6 +1126,8 @@ var cases = [ msgCompleteOnEOF: false, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 200, statusText: 'OK', headers: [ @@ -1071,6 +1155,8 @@ var cases = [ msgCompleteOnEOF: false, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 200, statusText: 'OK', headers: [ @@ -1106,6 +1192,8 @@ var cases = [ msgCompleteOnEOF: false, httpMajor: 1, httpMinor: 0, + method: null, + url: null, statusCode: 301, statusText: 'Moved Permanently', headers: [ @@ -1154,6 +1242,8 @@ var cases = [ msgCompleteOnEOF: false, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 200, statusText: 'OK', headers: [ @@ -1196,6 +1286,8 @@ var cases = [ msgCompleteOnEOF: false, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 500, statusText: 'Oriëntatieprobleem', headers: [ @@ -1221,6 +1313,8 @@ var cases = [ msgCompleteOnEOF: true, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 200, statusText: 'OK', headers: [ @@ -1241,6 +1335,8 @@ var cases = [ msgCompleteOnEOF: true, httpMajor: 1, httpMinor: 0, + method: null, + url: null, statusCode: 200, statusText: 'OK', headers: [ @@ -1261,6 +1357,8 @@ var cases = [ msgCompleteOnEOF: false, httpMajor: 1, httpMinor: 0, + method: null, + url: null, statusCode: 204, statusText: 'No content', headers: [ @@ -1280,6 +1378,8 @@ var cases = [ msgCompleteOnEOF: true, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 200, statusText: 'OK', headers: [], @@ -1296,6 +1396,8 @@ var cases = [ msgCompleteOnEOF: false, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 204, statusText: 'No content', headers: [], @@ -1313,6 +1415,8 @@ var cases = [ msgCompleteOnEOF: false, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 204, statusText: 'No content', headers: [ @@ -1335,6 +1439,8 @@ var cases = [ msgCompleteOnEOF: false, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 200, statusText: 'OK', headers: [ @@ -1373,6 +1479,8 @@ var cases = [ msgCompleteOnEOF: false, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 301, statusText: 'MovedPermanently', headers: [ @@ -1414,6 +1522,8 @@ var cases = [ msgCompleteOnEOF: true, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 200, statusText: '', headers: [], @@ -1452,7 +1562,7 @@ cases.forEach(function(testCase) { testCase.name); reqEvents.shift(); message = { - type: (method === undefined && url === undefined ? RESPONSE : REQUEST), + type: (method === null && url === null ? RESPONSE : REQUEST), shouldKeepAlive: shouldKeepAlive, //msgCompleteOnEOF httpMajor: versionMajor, @@ -1651,6 +1761,8 @@ cases.forEach(function(testCase) { msgCompleteOnEOF: false, httpMajor: 1, httpMinor: 0, + method: null, + url: null, statusCode: 200, statusText: 'OK', headers: [ @@ -1675,7 +1787,7 @@ cases.forEach(function(testCase) { statusCode, statusText, upgrade, shouldKeepAlive) { message = { - type: (method === undefined && url === undefined ? RESPONSE : REQUEST), + type: (method === null && url === null ? RESPONSE : REQUEST), shouldKeepAlive: shouldKeepAlive, //msgCompleteOnEOF httpMajor: versionMajor, @@ -1741,6 +1853,8 @@ testScan(getMessageByName('no merge with empty value'), msgCompleteOnEOF: true, httpMajor: 1, httpMinor: 1, + method: null, + url: null, statusCode: 200, statusText: 'OK', headers: [ @@ -1897,7 +2011,7 @@ function testScan(case1, case2, case3) { function onHeaders(versionMajor, versionMinor, headers, method, url, statusCode, statusText, upgrade, shouldKeepAlive) { message = { - type: (method === undefined && url === undefined ? RESPONSE : REQUEST), + type: (method === null && url === null ? RESPONSE : REQUEST), shouldKeepAlive: shouldKeepAlive, //msgCompleteOnEOF httpMajor: versionMajor, @@ -2029,7 +2143,7 @@ function testMultiple3(case1, case2, case3) { statusCode, statusText, upgrade, shouldKeepAlive) { message = { - type: (method === undefined && url === undefined ? RESPONSE : REQUEST), + type: (method === null && url === null ? RESPONSE : REQUEST), shouldKeepAlive: shouldKeepAlive, //msgCompleteOnEOF httpMajor: versionMajor, From 61e1b0b7c4da39ba324a556b689a8bbeb9c24dc5 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 22 Apr 2015 14:20:22 -0400 Subject: [PATCH 52/67] benchmark: add alternating inputs for http parser benchmarks --- benchmark/http/parser.js | 69 ++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/benchmark/http/parser.js b/benchmark/http/parser.js index 8107a3a52aa2b8..e00d8377b1cb36 100644 --- a/benchmark/http/parser.js +++ b/benchmark/http/parser.js @@ -9,12 +9,15 @@ var bench = common.createBenchmark(main, { type: [ 'small-req', 'small-res', + 'small-alternate', 'medium-req', 'medium-res', + 'medium-alternate', 'medium-req-chunked', 'medium-res-chunked', 'large-req', 'large-res', + 'large-alternate', 'large-req-chunked', 'large-res-chunked', ] @@ -29,6 +32,7 @@ var inputs = { 'HTTP/1.1 200 OK' + CRLF + 'Date: Mon, 23 May 2005 22:38:34 GMT' + CRLF + CRLF ], + 'small-alternate': true, 'medium-req': [ 'POST /it HTTP/1.1' + CRLF + 'Content-Type: text/plain' + CRLF + @@ -68,6 +72,7 @@ var inputs = { '123456789ABCDEF' + CRLF + '0' + CRLF ], + 'medium-alternate': true, 'medium-req-chunked': [ 'POST /it HTTP/', '1.1' + CRLF, @@ -112,6 +117,7 @@ var inputs = { 'Content-Length: 3572' + CRLF + CRLF + new Array(256).join('X-Filler: 42' + CRLF) + CRLF ], + 'large-alternate': true, 'large-req-chunked': ('POST /foo/bar/baz?quux=42#1337 HTTP/1.0' + CRLF + new Array(256).join('X-Filler: 42' + CRLF) + CRLF).match(/.{1,144}/g) @@ -133,30 +139,61 @@ function onComplete() { } function main(conf) { - var chunks = inputs[conf.type]; + var type = conf.type; + var chunks = inputs[type]; var n = +conf.n; - var nchunks = chunks.length; - var kind = (/\-req\-?/i.exec(conf.type) ? REQUEST : RESPONSE); + var nchunks = (chunks !== true ? chunks.length : 1); + var kind = (/\-req\-?/i.exec(type) ? REQUEST : RESPONSE); + var altsize = /^([^\-]+)\-alternate$/.exec(type); + var req; + var res; + + if (altsize) + altsize = altsize[1]; // Convert strings to Buffers first ... - for (var i = 0; i < nchunks; ++i) - chunks[i] = new Buffer(chunks[i], 'binary'); + if (chunks === true) { + // alternating + req = new Buffer(inputs[altsize + '-req'].join(''), 'binary'); + res = new Buffer(inputs[altsize + '-res'].join(''), 'binary'); + kind = REQUEST; + } else { + for (var i = 0; i < nchunks; ++i) + chunks[i] = new Buffer(chunks[i], 'binary'); + } var parser = new HTTPParser(kind); parser.onHeaders = onHeaders; parser.onBody = onBody; parser.onComplete = onComplete; - // Allow V8 to optimize first ... - for (var j = 0; j < 1000; ++j) { - for (var i = 0; i < nchunks; ++i) - parser.execute(chunks[i]); - } - - bench.start(); - for (var c = 0; c < n; ++c) { - for (var i = 0; i < nchunks; ++i) - parser.execute(chunks[i]); + if (altsize) { + // Allow V8 to optimize first ... + for (var j = 0; j < 1000; ++j) { + parser.reinitialize(REQUEST); + parser.execute(req); + parser.reinitialize(RESPONSE); + parser.execute(res); + } + bench.start(); + for (var c = 0; c < n; ++c) { + parser.reinitialize(REQUEST); + parser.execute(req); + parser.reinitialize(RESPONSE); + parser.execute(res); + } + bench.end(n * 2); + } else { + // Allow V8 to optimize first ... + for (var j = 0; j < 1000; ++j) { + for (var i = 0; i < nchunks; ++i) + parser.execute(chunks[i]); + } + bench.start(); + for (var c = 0; c < n; ++c) { + for (var i = 0; i < nchunks; ++i) + parser.execute(chunks[i]); + } + bench.end(n * nchunks); } - bench.end(n * nchunks); } From f7c90ff456195d7a44b1d175b8a4ff101e97a646 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 22 Apr 2015 14:23:07 -0400 Subject: [PATCH 53/67] test: move http parser durability test to pummel These tests take significantly longer to execute. Also the http 0.9 test has been corrected to reflect the capability of the current js parser. --- .../test-http-parser-durability.js | 76 ++++++++++++++++--- 1 file changed, 65 insertions(+), 11 deletions(-) rename test/{parallel => pummel}/test-http-parser-durability.js (97%) diff --git a/test/parallel/test-http-parser-durability.js b/test/pummel/test-http-parser-durability.js similarity index 97% rename from test/parallel/test-http-parser-durability.js rename to test/pummel/test-http-parser-durability.js index ac7614e583d251..289ef30faf8741 100644 --- a/test/parallel/test-http-parser-durability.js +++ b/test/pummel/test-http-parser-durability.js @@ -5,6 +5,7 @@ var format = require('util').format; var HTTPParser = require('_http_parser'); var CRLF = '\r\n'; +var LF = '\n'; var REQUEST = HTTPParser.REQUEST; var RESPONSE = HTTPParser.RESPONSE; var requestsEnd = -1; @@ -514,8 +515,9 @@ var cases = [ ].join(CRLF), shouldKeepAlive: false, msgCompleteOnEOF: false, - httpMajor: 0, - httpMinor: 9, + error: true, + httpMajor: 1, + httpMinor: null, method: 'GET', url: '/', statusCode: null, @@ -552,7 +554,7 @@ var cases = [ body: undefined }, { - name: 'line folding in header value', + name: 'line folding in header value with CRLF', type: REQUEST, raw: [ 'GET / HTTP/1.1', @@ -879,6 +881,48 @@ var cases = [ headers: [], body: undefined }, + { + name: 'line folding in header value with LF', + type: REQUEST, + raw: [ + 'GET / HTTP/1.1', + 'Line1: abc', + '\tdef', + ' ghi', + '\t\tjkl', + ' mno ', + '\t \tqrs', + 'Line2: \t line2\t', + 'Line3:', + ' line3', + 'Line4: ', + ' ', + 'Connection:', + ' close', + '', '' + ].join(LF), + shouldKeepAlive: false, + msgCompleteOnEOF: false, + httpMajor: 1, + httpMinor: 1, + method: 'GET', + url: '/', + statusCode: null, + statusText: null, + headers: [ + 'Line1', + 'abc def ghi jkl mno qrs', + 'Line2', + 'line2', + 'Line3', + 'line3', + 'Line4', + '', + 'Connection', + 'close', + ], + body: undefined + }, { name: 'multiple connection header values with folding', type: REQUEST, @@ -1626,6 +1670,10 @@ cases.forEach(function(testCase) { throw new Error('Unexpected error for: ' + testCase.name + ':\n\n' + ret.stack + '\n'); } + if (testCase.error !== undefined) { + completed = true; // Prevent error from throwing on script exit + return; + } if (message.upgrade === false || typeof ret !== 'number') message.upgrade = undefined; else @@ -1658,7 +1706,7 @@ cases.forEach(function(testCase) { for (var i = 0; i < 10000; ++i) { ret = parser.execute(input); if (typeof ret !== 'number') { - assert(/Header size limit exceeded/i.test(ret.message)); + assert(/Header limit exceeded/i.test(ret.message)); return; } } @@ -1724,7 +1772,7 @@ cases.forEach(function(testCase) { assert.strictEqual(ret, Buffer.byteLength(input)); else { assert.strictEqual(typeof ret !== 'number', true); - assert.strictEqual(/Chunk size too big/i.test(ret.message), true); + assert.strictEqual(/Chunk size limit exceeded/i.test(ret.message), true); } } ); @@ -1733,13 +1781,16 @@ cases.forEach(function(testCase) { (function() { var responsesStart = requestsEnd + 1; for (var i = responsesStart; i < cases.length; ++i) { - if (!cases[i].shouldKeepAlive) + if (!cases[i].shouldKeepAlive || cases[i].error !== undefined) continue; for (var j = responsesStart; j < cases.length; ++j) { - if (!cases[j].shouldKeepAlive) + if (!cases[j].shouldKeepAlive || cases[j].error !== undefined) continue; - for (var k = responsesStart; k < cases.length; ++k) + for (var k = responsesStart; k < cases.length; ++k) { + if (cases[i].error !== undefined) + continue; testMultiple3(cases[i], cases[j], cases[k]); + } } } })(); @@ -1956,13 +2007,16 @@ console.log('responses okay'); // Test pipelined requests (function() { for (var i = 0; i <= requestsEnd; ++i) { - if (!cases[i].shouldKeepAlive) + if (!cases[i].shouldKeepAlive || cases[i].error !== undefined) continue; for (var j = 0; j <= requestsEnd; ++j) { - if (!cases[j].shouldKeepAlive) + if (!cases[j].shouldKeepAlive || cases[j].error !== undefined) continue; - for (var k = 0; k <= requestsEnd; ++k) + for (var k = 0; k <= requestsEnd; ++k) { + if (cases[k].error !== undefined) + continue; testMultiple3(cases[i], cases[j], cases[k]); + } } } })(); From 05480acf443bc15be661215f9ba11dfdc1931a22 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 22 Apr 2015 14:24:20 -0400 Subject: [PATCH 54/67] test: fix test to reflect lack of HTTP 0.9 support --- test/parallel/test-https-foafssl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-https-foafssl.js b/test/parallel/test-https-foafssl.js index 11d628ff1ff598..256379d3241fbd 100644 --- a/test/parallel/test-https-foafssl.js +++ b/test/parallel/test-https-foafssl.js @@ -62,7 +62,7 @@ server.listen(common.PORT, function() { server.close(); }); - client.stdin.write('GET /\r\n'); + client.stdin.write('GET / HTTP/1.0\r\n\r\n'); client.on('error', function(error) { throw error; From c48053a6bfa688257b5757c9e61d5a014b9bf16f Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 22 Apr 2015 14:33:32 -0400 Subject: [PATCH 55/67] http: switch to regexp-less, LF-compatible js parser See this[1] comment for more details and benchmark results. Notable changes: * Removed all regexps. Only request URLs are not validated currently. * Added globally configurable start line length limits * Chunk size parsing no longer line buffers * Lines can end in just LF too * Faster than the original regexp-based implementation [1] https://github.com/iojs/io.js/pull/1457#issuecomment-94941637 --- lib/_http_parser.js | 851 ++++++++++++++++++-------------------------- 1 file changed, 352 insertions(+), 499 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index b723cfed74a1f6..ffc9902d798c2d 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -24,8 +24,6 @@ Misc differences with joyent/http-parser: joyent/http-parser keeps trailing whitespace. This parser keeps neither preceding nor trailing whitespace. - * Enforces CRLF for line endings instead of additionally allowing just LF. - * Does not allow spaces (which are invalid) in header field names. * Smaller maximum chunk/content length (2^53-1 vs 2^64-2). Obviously it's @@ -46,183 +44,32 @@ var LF = 10; var MAX_CHUNK_SIZE = Number.MAX_SAFE_INTEGER; // 9007199254740991 -var UNHEX = { - 48: 0, - 49: 1, - 50: 2, - 51: 3, - 52: 4, - 53: 5, - 54: 6, - 55: 7, - 56: 8, - 57: 9, - 65: 10, - 66: 11, - 67: 12, - 68: 13, - 69: 14, - 70: 15, - 97: 10, - 98: 11, - 99: 12, - 100: 13, - 101: 14, - 102: 15 -}; - -// RFC 7230 recommends at least 8000 max bytes for request line, but no -// recommendation for status lines +// RFC 7230 recommends HTTP implementations support at least 8000 bytes for +// the request line. We use 8190 by default, the same as Apache. +HTTPParser.MAX_REQ_LINE = 8190; +// RFC 7230 does not have any recommendations for minimum response line length +// support. Judging by the (current) longest standard status reason text, the +// typical response line will be 44 bytes or less (not including (CR)LF). Since +// the reason text field is free form though, we will roughly triple that +// amount for the default. +HTTPParser.MAX_RES_LINE = 128; + +// This is the total limit for start line + all headers was copied from +// joyent/http-parser. var MAX_HEADER_BYTES = 80 * 1024; -var RE_CONN_CLOSE = /(?:^|[\t ,]+)close(?:$|[\t ,]+)/i; -var RE_CONN_KEEPALIVE = /(?:^|[\t ,]+)keep\-alive(?:$|[\t ,]+)/i; -var RE_CONN_UPGRADE = /(?:^|[\t ,]+)upgrade(?:$|[\t ,]+)/i; -var RE_TE_CHUNKED = /(?:^|[\t ,]+)chunked(?:$|[\t ,]+)/i; +var RE_CONN_CLOSE = /(?:^|[\t ,]+)close(?:\r?$||[\t ,]+)/i; +var RE_CONN_KEEPALIVE = /(?:^|[\t ,]+)keep\-alive(?:\r?$|[\t ,]+)/i; +var RE_CONN_UPGRADE = /(?:^|[\t ,]+)upgrade(?:\r?$|[\t ,]+)/i; +var RE_TE_CHUNKED = /(?:^|[\t ,]+)chunked(?:\r?$|[\t ,]+)/i; var CC_CONNECT = 'connect'.split('').map(getFirstCharCode); var CC_CONNECTION = 'connection'.split('').map(getFirstCharCode); var CC_TE = 'transfer-encoding'.split('').map(getFirstCharCode); var CC_UPGRADE = 'upgrade'.split('').map(getFirstCharCode); var CC_CONTLEN = 'content-length'.split('').map(getFirstCharCode); - -// URI-parsing Regular Expressions ... - -// Note: double quotes are not allowed anywhere in request URIs, but -// joyent/http-parser allowed it previously so we do too for better backwards -// compatibility ... -// Note: non-ASCII characters are not allowed anywhere in request URIs, but -// joyent/http-parser allowed it previously so we do too for better backwards -// compatibility ... -var RE_PCHAR = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:@"\x80-\xFF]|%[0-9A-Fa-f]{2})/; -var RE_ABS_PATH = new RegExp('(?:/' + RE_PCHAR.source + '*)+'); -// Note: double quotes are not allowed anywhere in request URIs, but -// joyent/http-parser allowed it previously so we do too for better backwards -// compatibility ... -// Note: non-ASCII characters are not allowed anywhere in request URIs, but -// joyent/http-parser allowed it previously so we do too for better backwards -// compatibility ... -var RE_QUERY = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:@/?"\x80-\xFF]|%[0-9A-Fa-f]{2})*/; -// Note: fragments are technically not allowed in the request line, but -// joyent/http-parser allowed it previously so we do too for better backwards -// compatibility ... -var RE_ORIGIN_FORM = new RegExp('(?:' + RE_ABS_PATH.source + '(?:\\?' + - RE_QUERY.source + ')?(?:#' + RE_QUERY.source + - ')?)'); - -var RE_SCHEME = /[A-Za-z][A-Za-z0-9+\-.]*/; -var RE_USERINFO = /(?:[A-Za-z0-9\-._~!$&'()*+,;=:]|%[0-9A-Fa-f]{2})*/; -var RE_IPV4_OCTET = /(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/; -var RE_IPV4 = new RegExp('(?:' + RE_IPV4_OCTET.source + '\\.){3}' + - RE_IPV4_OCTET.source); -var RE_H16 = /[0-9A-Fa-f]{1,4}/; -var RE_LS32 = new RegExp('(?:' + RE_H16.source + ':' + RE_H16.source + ')|(?:' + - RE_IPV4.source + ')'); -var RE_H16_COLON = new RegExp('(?:' + RE_H16.source + ':)'); -var RE_IPV6 = new RegExp('(?:' + - // Begin LS32 postfix cases - '(?:' + - [ - RE_H16_COLON.source + '{6}', - '::' + RE_H16_COLON.source + '{5}', - '(?:' + RE_H16.source + ')?::' + RE_H16_COLON.source + '{4}', - '(?:' + RE_H16_COLON.source + '{0,1}' + RE_H16.source + ')?::' + - RE_H16_COLON.source + '{3}', - '(?:' + RE_H16_COLON.source + '{0,2}' + RE_H16.source + ')?::' + - RE_H16_COLON.source + '{2}', - '(?:' + RE_H16_COLON.source + '{0,3}' + RE_H16.source + ')?::' + - RE_H16_COLON.source, - '(?:' + RE_H16_COLON.source + '{0,4}' + RE_H16.source + ')?::', - ].join(')|(?:') + - ')(?:' + RE_LS32.source + ')' + - // End LS32 postfix cases - ')' + - '|(?:(?:' + RE_H16_COLON.source + '{0,5}' + RE_H16.source + ')?::' + - RE_H16.source + ')' + - '|(?:(?:' + RE_H16_COLON.source + '{0,6}' + RE_H16.source + ')?::)'); -var RE_REGNAME = /(?:[A-Za-z0-9\-._~!$&'()*+,;=]|%[0-9A-Fa-f]{2})*/; -var RE_HOST = new RegExp('(?:(?:\\[' + RE_IPV6.source + '\\])|(?:' + - RE_IPV4.source + ')|' + RE_REGNAME.source + ')'); -var RE_AUTHORITY = new RegExp('(?:(?:' + RE_USERINFO.source + '@)?' + - RE_HOST.source + '(?::[0-9]*)?)'); -var RE_PATH_ABEMPTY = new RegExp('(?:/' + RE_PCHAR.source + '*)*'); -var RE_PATH_ROOTLESS = new RegExp('(?:' + RE_PCHAR.source + '+' + - RE_PATH_ABEMPTY.source + ')'); -var RE_PATH_ABSOLUTE = new RegExp('(?:/' + RE_PATH_ROOTLESS.source + '?)'); -var RE_HIER_PART = new RegExp('(?:(?://' + RE_AUTHORITY.source + - RE_PATH_ABEMPTY.source + ')|' + - RE_PATH_ABSOLUTE.source + '|' + - RE_PATH_ROOTLESS.source + '|)'); -// Note: fragments are technically not allowed in the request line, but -// joyent/http-parser allowed it previously so we do too for better backwards -// compatibility ... -var RE_ABSOLUTE_FORM = new RegExp('(?:' + RE_SCHEME.source + ':' + - RE_HIER_PART.source + '(?:\\?' + - RE_QUERY.source + ')?(?:#' + - RE_QUERY.source + ')?)'); - -var RE_REQUEST_TARGET = new RegExp('(?:' + RE_ORIGIN_FORM.source + '|' + - RE_ABSOLUTE_FORM.source + '|' + - RE_AUTHORITY.source + '|\\*)'); -var RE_REQUEST_LINE = new RegExp('^([!#$%\'*+\\-.^_`|~0-9A-Za-z]+) (' + - RE_REQUEST_TARGET.source + - ')(?: HTTP\\/1\\.([01]))?$'); -/* -request-target = origin-form | absolute-form | authority-form | - asterisk-form - origin-form = absolute-path [ "?" query ] - absolute-path = 1*( "/" segment ) - segment = *pchar - pchar = unreserved | pct-encoded | sub-delims | ":" | "@" - unreserved = ALPHA | DIGIT | "-" | "." | "_" | "~" - pct-encoded = "%" HEXDIG HEXDIG - HEXDIG = DIGIT | "A" | "B" | "C" | "D" | "E" | "F" - sub-delims = "!" | "$" | "&" | "'" | "(" | ")" | "*" | "+" | - "," | ";" | "=" - query = *( pchar | "/" | "?" ) - absolute-form = absolute-URI - absolute-URI = scheme ":" hier-part [ "?" query ] - scheme = alpha *( alpha | digit | "+" | "-" | "." ) - hier-part = "//" authority path-abempty | path-absolute | - path-rootless | path-empty - authority = [ userinfo "@" ] host [ ":" port ] - userinfo = *( unreserved | pct-encoded | sub-delims | ":" ) - host = IP-literal | IPv4address | reg-name - IP-literal = "[" ( IPv6address | IPvFuture ) "]" - IPv6address = 6( h16 ":" ) ls32 - | "::" 5( h16 ":" ) ls32 - | [ h16 ] "::" 4( h16 ":" ) ls32 - | [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 - | [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 - | [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 - | [ *4( h16 ":" ) h16 ] "::" ls32 - | [ *5( h16 ":" ) h16 ] "::" h16 - | [ *6( h16 ":" ) h16 ] "::" - h16 = 1*4HEXDIG - ls32 = ( h16 ":" h16 ) | IPv4address - IPv4address = dec-octet "." dec-octet "." dec-octet "." - dec-octet - dec-octet = DIGIT ; 0-9 - | %x31-39 DIGIT ; 10-99 - | "1" 2DIGIT ; 100-199 - | "2" %x30-34 DIGIT ; 200-249 - | "25" %x30-35 ; 250-255 - reg-name = *( unreserved | pct-encoded | sub-delims ) - port = *DIGIT - path-abempty = *( "/" segment ) - path-absolute = "/" [ segment-nz *( "/" segment ) ] - segment-nz = 1*pchar - path-rootless = segment-nz *( "/" segment ) - path-empty = 0 - authority-form = authority - asterisk-form = "*" -*/ - -// Note: AT LEAST a space is technically required after the status code, but -// joyent/http-parser allows a CRLF immediately following the status code, so we -// do also for backwards compatibility ... -var RE_STATUS_LINE = /^HTTP\/1\.([01]) ([0-9]{3})(?: (.*))?$/; - -var RE_HEADER = /^([!#$%'*+\-.^_`|~0-9A-Za-z]+):(.*)$/; +var REQ_HTTP_VER_BYTES = ' HTTP/1.'.split('').map(getFirstCharCode); +var RES_HTTP_VER_BYTES = REQ_HTTP_VER_BYTES.slice(1); +REQ_HTTP_VER_BYTES.reverse(); var STATE_REQ_LINE = 0; var STATE_STATUS_LINE = 1; @@ -230,9 +77,10 @@ var STATE_HEADER = 2; var STATE_BODY_LITERAL = 3; var STATE_BODY_EOF = 4; var STATE_BODY_CHUNKED_SIZE = 5; -var STATE_BODY_CHUNKED_BYTES = 6; -var STATE_BODY_CHUNKED_BYTES_CRLF = 7; -var STATE_COMPLETE = 8; +var STATE_BODY_CHUNKED_SIZE_IGNORE = 6; +var STATE_BODY_CHUNKED_BYTES = 7; +var STATE_BODY_CHUNKED_BYTES_LF = 8; +var STATE_COMPLETE = 9; var FLAG_CHUNKED = 1 << 0; var FLAG_CONNECTION_KEEP_ALIVE = 1 << 1; @@ -267,11 +115,10 @@ HTTPParser.prototype.reinitialize = function(type) { this._err = null; this._flags = 0; this._contentLen = null; - this._nbytes = 0; + this._nbytes = null; this._nhdrbytes = 0; this._nhdrpairs = 0; this._buf = ''; - this._seenCR = false; // common properties this.headers = []; @@ -309,129 +156,6 @@ HTTPParser.prototype._setError = function(msg) { this._err = err; return err; }; -HTTPParser.prototype._processHdrLine = function(line) { - switch (this._state) { - case STATE_HEADER: - if (line.length === 0) { - // We saw a double CRLF - this._headersEnd(); - return; - } - var headers = this.headers; - var headerslen = headers.length; - var fieldName; - var fieldValue; - var m = RE_HEADER.exec(line); - if (m === null) { - var firstChr = line.charCodeAt(0); - if (firstChr !== 32 & firstChr !== 9) - return this._setError('Malformed header line'); - // RFC 7230 compliant, but less backwards compatible: - var extra = ltrim(line); - if (extra.length > 0) { - if (headerslen === 0) - return this._setError('Malformed header line'); - fieldName = headers[headerslen - 2]; - fieldValue = headers[headerslen - 1] + ' ' + extra; - // Need to re-check value since matched values may now exist ... - if (equalsLower(fieldName, CC_CONNECTION)) { - if (fieldValue.search(RE_CONN_CLOSE) > -1) - this._flags |= FLAG_CONNECTION_CLOSE; - if (fieldValue.search(RE_CONN_KEEPALIVE) > -1) - this._flags |= FLAG_CONNECTION_KEEP_ALIVE; - if (fieldValue.search(RE_CONN_UPGRADE) > -1) - this._flags |= FLAG_CONNECTION_UPGRADE; - } else if (equalsLower(fieldName, CC_TE)) { - if (fieldValue.search(RE_TE_CHUNKED) > -1) - this._flags |= FLAG_CHUNKED; - } else if (equalsLower(fieldName, CC_UPGRADE)) { - this._flags |= FLAG_UPGRADE; - } else if (equalsLower(fieldName, CC_CONTLEN)) { - var val = parseInt(fieldValue, 10); - if (val !== val || val > MAX_CHUNK_SIZE) - return this._setError('Bad Content-Length: ' + inspect(val)); - this._contentLen = val; - } - headers[headerslen - 1] = fieldValue; - } - } else { - // Ensures that trailing whitespace after the last folded line for - // header values gets trimmed - if (headerslen > 0) - headers[headerslen - 1] = trim(headers[headerslen - 1]); - // m[1]: field name - // m[2]: field value - fieldName = m[1]; - fieldValue = m[2]; - if (equalsLower(fieldName, CC_CONNECTION)) { - if (fieldValue.search(RE_CONN_CLOSE) > -1) - this._flags |= FLAG_CONNECTION_CLOSE; - if (fieldValue.search(RE_CONN_KEEPALIVE) > -1) - this._flags |= FLAG_CONNECTION_KEEP_ALIVE; - if (fieldValue.search(RE_CONN_UPGRADE) > -1) - this._flags |= FLAG_CONNECTION_UPGRADE; - } else if (equalsLower(fieldName, CC_TE)) { - if (fieldValue.search(RE_TE_CHUNKED) > -1) - this._flags |= FLAG_CHUNKED; - } else if (equalsLower(fieldName, CC_UPGRADE)) { - this._flags |= FLAG_UPGRADE; - } else if (equalsLower(fieldName, CC_CONTLEN)) { - var val = parseInt(fieldValue, 10); - if (val !== val || val > MAX_CHUNK_SIZE) - return this._setError('Bad Content-Length: ' + inspect(val)); - this._contentLen = val; - } - var maxHeaderPairs = this.maxHeaderPairs; - if (maxHeaderPairs <= 0 || ++this._nhdrpairs < maxHeaderPairs) - headers.push(fieldName, fieldValue); - } - break; - case STATE_REQ_LINE: - // Original HTTP parser ignored blank lines before request/status line, - // so we do that here too ... - if (line.length === 0) - return true; - var m = RE_REQUEST_LINE.exec(line); - if (m === null) - return this._setError('Malformed request line'); - // m[1]: HTTP method - // m[2]: request target - // m[3]: HTTP minor version - this.method = m[1]; - this.url = m[2]; - var minor = m[3]; - if (minor === undefined) { - // HTTP/0.9 ugh... - if (m[1] !== 'GET') - return this._setError('Malformed request line'); - this.httpMajor = 0; - this.httpMinor = 9; - this._headersEnd(); - } else { - this.httpMinor = (minor === '1' ? 1 : 0); - this._state = STATE_HEADER; - } - break; - case STATE_STATUS_LINE: - // Original HTTP parser ignored blank lines before request/status line, - // so we do that here too ... - if (line.length === 0) - return true; - var m = RE_STATUS_LINE.exec(line); - if (m === null) - return this._setError('Malformed status line'); - // m[1]: HTTP minor version - // m[2]: HTTP status code - // m[3]: Reason text - this.httpMinor = (m[1] === '1' ? 1 : 0); - this.statusCode = parseInt(m[2], 10); - this.statusText = m[3] || ''; - this._state = STATE_HEADER; - break; - default: - return this._setError('Unexpected HTTP parser state: ' + this._state); - } -}; HTTPParser.prototype._headersEnd = function() { var flags = this._flags; var type = this.type; @@ -447,8 +171,7 @@ HTTPParser.prototype._headersEnd = function() { var ret; this._buf = ''; - this._seenCR = false; - this._nbytes = 0; + this._nbytes = null; if ((flags & FLAG_CHUNKED) > 0) { this._state = STATE_BODY_CHUNKED_SIZE; @@ -504,8 +227,10 @@ HTTPParser.prototype._executeStartLine = function(data) { if (data.length === 0) return 0; var firstByte = data[0]; - if ((firstByte < 32 || firstByte >= 127) && firstByte !== CR) - return this._setError('Invalid byte(s) in start line'); + if ((firstByte < 32 || firstByte >= 127) && firstByte !== CR && + firstByte !== LF) { + return this._setError('Invalid byte in start line'); + } this.execute = this._executeHeader; return this.execute(data); }; @@ -516,25 +241,40 @@ HTTPParser.prototype._executeHeader = function(data) { return 0; var offset = 0; - var seenCR = this._seenCR; var buf = this._buf; var nhdrbytes = this._nhdrbytes; + var state = this._state; + var headers = this.headers; + var headerslen = headers.length; + var maxHeaderPairs = this.maxHeaderPairs; var ret; while (offset < len) { - if (seenCR) { - seenCR = false; - if (data[offset] === LF) { - // Our internal buffer contains a full line - ++offset; - ret = this._processHdrLine(buf); - buf = ''; - if (typeof ret === 'object') - return ret; - else if (ret === undefined) { - var state = this._state; - if (state !== STATE_HEADER) { - // Begin of body or end of message + ret = indexOfLF(data, len, offset); + if (ret > -1) { + // Our internal buffer contains a full line + var bytesToAdd = ret - offset; + if (bytesToAdd > 0) { + nhdrbytes += bytesToAdd; + if (state === STATE_REQ_LINE && nhdrbytes > HTTPParser.MAX_REQ_LINE) + return this._setError('Request line limit exceeded'); + else if (state === STATE_STATUS_LINE && + nhdrbytes > HTTPParser.MAX_RES_LINE) { + return this._setError('Response line limit exceeded'); + } else if (nhdrbytes > MAX_HEADER_BYTES) + return this._setError('Header limit exceeded'); + buf += data.toString('binary', offset, ret); + } + + offset = ret + 1; + var buflen = buf.length; + + switch (state) { + case STATE_HEADER: + if (buflen === 0 || buf.charCodeAt(0) === CR) { + // We saw a double line ending + this._headersEnd(); + state = this._state; if (state < STATE_COMPLETE && offset < len) { // Execute extra body bytes ret = this.execute(data.slice(offset)); @@ -545,71 +285,219 @@ HTTPParser.prototype._executeHeader = function(data) { this.reinitialize(this.type); return offset; } - } - } else { - // False match - buf += '\r'; - ++nhdrbytes; - if (nhdrbytes > MAX_HEADER_BYTES) { - return this._setError('Header size limit exceeded (' + - MAX_HEADER_BYTES + ')'); - } - } - } - ret = indexOfCRLF(data, len, offset); - if (ret > -1) { - // Our internal buffer contains a full line - var bytesToAdd = ret - offset; - if (bytesToAdd > 0) { - nhdrbytes += bytesToAdd; - if (nhdrbytes > MAX_HEADER_BYTES) { - return this._setError('Header size limit exceeded (' + - MAX_HEADER_BYTES + ')'); - } - buf += data.toString('binary', offset, ret); + var idx = -1; + var fieldName; + var fieldValue; + var valueStart = -1; + var validFieldName = true; + for (var i = 0; i < buflen; ++i) { + var ch = buf.charCodeAt(i); + if (idx === -1) { + if (ch === 58) { // ':' + if (i === 0 || !validFieldName) + return this._setError('Malformed header line'); + idx = i; + } else if (ch < 33 || ch > 126) + validFieldName = false; + } else if (ch !== 32 && ch !== 9) { + valueStart = i; + break; + } + } + if (idx === -1) { + var firstChr = buf.charCodeAt(0); + if (firstChr !== 32 & firstChr !== 9) + return this._setError('Malformed header line'); + // RFC 7230 compliant, but less backwards compatible: + var extra = ltrim(buf); + if (extra.length > 0) { + if (headerslen === 0) + return this._setError('Malformed header line'); + fieldName = headers[headerslen - 2]; + fieldValue = headers[headerslen - 1]; + if (fieldValue.length > 0) { + if (fieldValue.charCodeAt(fieldValue.length - 1) === CR) + fieldValue = fieldValue.slice(0, -1); + if (fieldValue.length > 0) + fieldValue += ' ' + extra; + else + fieldValue = extra; + } else + fieldValue = extra; + // Need to re-check value since matched values may now exist ... + if (equalsLower(fieldName, CC_CONNECTION)) { + if (fieldValue.search(RE_CONN_CLOSE) > -1) + this._flags |= FLAG_CONNECTION_CLOSE; + if (fieldValue.search(RE_CONN_KEEPALIVE) > -1) + this._flags |= FLAG_CONNECTION_KEEP_ALIVE; + if (fieldValue.search(RE_CONN_UPGRADE) > -1) + this._flags |= FLAG_CONNECTION_UPGRADE; + } else if (equalsLower(fieldName, CC_TE)) { + if (fieldValue.search(RE_TE_CHUNKED) > -1) + this._flags |= FLAG_CHUNKED; + } else if (equalsLower(fieldName, CC_UPGRADE)) { + this._flags |= FLAG_UPGRADE; + } else if (equalsLower(fieldName, CC_CONTLEN)) { + var val = parseInt(fieldValue, 10); + if (val !== val || val > MAX_CHUNK_SIZE) + return this._setError('Bad Content-Length: ' + inspect(val)); + this._contentLen = val; + } + headers[headerslen - 1] = fieldValue; + } + } else { + fieldName = buf.slice(0, idx); + fieldValue = valueStart === -1 ? '' : buf.slice(valueStart); + // Ensures that trailing whitespace after the last folded line for + // header values gets trimmed + if (headerslen > 0) + headers[headerslen - 1] = rtrim(headers[headerslen - 1]); + if (equalsLower(fieldName, CC_CONNECTION)) { + if (fieldValue.search(RE_CONN_CLOSE) > -1) + this._flags |= FLAG_CONNECTION_CLOSE; + if (fieldValue.search(RE_CONN_KEEPALIVE) > -1) + this._flags |= FLAG_CONNECTION_KEEP_ALIVE; + if (fieldValue.search(RE_CONN_UPGRADE) > -1) + this._flags |= FLAG_CONNECTION_UPGRADE; + } else if (equalsLower(fieldName, CC_TE)) { + if (fieldValue.search(RE_TE_CHUNKED) > -1) + this._flags |= FLAG_CHUNKED; + } else if (equalsLower(fieldName, CC_UPGRADE)) { + this._flags |= FLAG_UPGRADE; + } else if (equalsLower(fieldName, CC_CONTLEN)) { + var val = parseInt(fieldValue, 10); + if (val !== val || val > MAX_CHUNK_SIZE) + return this._setError('Bad Content-Length: ' + inspect(val)); + this._contentLen = val; + } + if (maxHeaderPairs <= 0 || ++this._nhdrpairs < maxHeaderPairs) { + headers.push(fieldName, fieldValue); + headerslen += 2; + } + } + break; + case STATE_REQ_LINE: + // Original HTTP parser ignored blank lines before request/status + // line, so we do that here too ... + if (buflen === 0 || buf.charCodeAt(0) === CR) + break; + + var firstSP; + var urlStart; + var urlEnd; + var minor; + var end = (buf.charCodeAt(buflen - 1) === CR ? + buflen - 3 : buflen - 2); + // Start working backwards and both validate that the line ends in + // ` HTTP/1.[01]` and find the end of the URL (in case there are + // multiple spaces/tabs separating the URL and HTTP version + var ch = buf.charCodeAt(end + 1); + if (ch === 49) + minor = 1; + else if (ch === 48) + minor = 0; + else + return this._setError('Malformed request line'); + var h = 0; + while (end >= 0) { + var ch = buf.charCodeAt(end); + if (h < 8) { + if (ch !== REQ_HTTP_VER_BYTES[h++]) + return this._setError('Malformed request line'); + } else if (ch >= 33 && ch !== 127) { + urlEnd = end + 1; + break; + } + --end; + } + if (urlEnd === undefined) + return this._setError('Malformed request line'); + + // Now start working forwards and both validate the HTTP method and + // find the start of the URL (in case there are multiple spaces/tabs + // separating the method and the URL + for (var i = 0; i < urlEnd; ++i) { + ch = buf.charCodeAt(i); + if (firstSP !== undefined) { + if (ch >= 33 && ch !== 127) { + urlStart = i; + break; + } + } else if (ch === 32) + firstSP = i; + else if (ch < 33 || ch > 126) + return this._setError('Malformed request line'); + } + if (firstSP === undefined || + urlStart === undefined || + urlStart === urlEnd) { + return this._setError('Malformed request line'); + } + + this.httpMinor = minor; + this.method = buf.slice(0, firstSP); + this.url = buf.slice(urlStart, urlEnd); + state = STATE_HEADER; + break; + case STATE_STATUS_LINE: + // Original HTTP parser ignored blank lines before request/status + // line, so we do that here too ... + if (buflen === 0 || buf.charCodeAt(0) === CR) + break; + + // Validate HTTP version + for (var h = 0; i < 7; ++h) { + if (buf.charCodeAt(i) !== RES_HTTP_VER_BYTES[h]) + return this._setError('Malformed status line'); + } + var minor; + var status = 0; + if (buf.charCodeAt(7) === 49) + minor = 1; + else if (buf.charCodeAt(7) === 48) + minor = 0; + else + return this._setError('Malformed status line'); + if (buf.charCodeAt(8) !== 32) + return this._setError('Malformed status line'); + + // Validate status code + for (var i = 9; i < 12; ++i) { + var ch = buf.charCodeAt(i); + if (ch < 48 || ch > 57) + return this._setError('Malformed status line'); + status *= 10; + status += (ch - 48); + } + + if (buf.charCodeAt(buflen - 1)) + --buflen; + this.httpMinor = minor; + this.statusCode = status; + this.statusText = (buflen > 13 ? buf.slice(13, buflen) : ''); + state = STATE_HEADER; + break; + default: + return this._setError('Unexpected HTTP parser state: ' + state); } - offset = ret + 2; - ret = this._processHdrLine(buf); + buf = ''; - if (typeof ret === 'object') - return ret; - else if (ret === undefined) { - var state = this._state; - if (state !== STATE_HEADER) { - // Begin of body or end of message - if (state < STATE_COMPLETE && offset < len) { - // Execute extra body bytes - ret = this.execute(data.slice(offset)); - if (typeof ret !== 'number') - return ret; - return offset + ret; - } else if (state === STATE_COMPLETE) - this.reinitialize(this.type); - return offset; - } - } } else { - // Check for possible cross-chunk CRLF split - var end; - if (data[len - 1] === CR) { - seenCR = true; - end = len - 1; - } else - end = len; - - nhdrbytes += end - offset; - - if (nhdrbytes > MAX_HEADER_BYTES) { - return this._setError('Header size limit exceeded (' + - MAX_HEADER_BYTES + ')'); - } - buf += data.toString('binary', offset, end); + nhdrbytes += len - offset; + if (state === STATE_REQ_LINE && nhdrbytes > HTTPParser.MAX_REQ_LINE) + return this._setError('Request line limit exceeded'); + else if (state === STATE_STATUS_LINE && + nhdrbytes > HTTPParser.MAX_RES_LINE) { + return this._setError('Response line limit exceeded'); + } else if (nhdrbytes > MAX_HEADER_BYTES) + return this._setError('Header limit exceeded'); + buf += data.toString('binary', offset); break; } } + this._state = state; this._buf = buf; - this._seenCR = seenCR; this._nhdrbytes = nhdrbytes; return len; @@ -621,91 +509,28 @@ HTTPParser.prototype._executeBodyChunked = function(data) { return 0; var offset = 0; - var seenCR = this._seenCR; - var buf = this._buf; var nbytes = this._nbytes; - var ret; - var bytesToAdd; + var state = this._state; + var dec; while (offset < len) { - switch (this._state) { + switch (state) { case STATE_BODY_CHUNKED_SIZE: - if (seenCR) { - seenCR = false; - if (data[offset] === LF) { - // Our internal buffer contains a full line - ++offset; - ret = readChunkSize(buf); - buf = ''; - if (typeof ret !== 'number') { - this.execute = this._executeError; - this._err = ret; - return ret; - } else if (ret === 0) { - this._seenCR = false; - this._buf = ''; - this._flags |= FLAG_TRAILING; - this._state = STATE_HEADER; - this.execute = this._executeHeader; - if (offset < len) { - ret = this.execute(data.slice(offset)); - if (typeof ret !== 'number') - return ret; - return offset + ret; - } - return offset; - } else { - nbytes = ret; - this._state = STATE_BODY_CHUNKED_BYTES; - continue; - } - } else { - // False match - buf += '\r'; - } - } - ret = indexOfCRLF(data, len, offset); - if (ret > -1) { - // Our internal buffer contains a full line - bytesToAdd = ret - offset; - if (bytesToAdd > 0) - buf += data.toString('ascii', offset, ret); - - offset = ret + 2; - ret = readChunkSize(buf); - buf = ''; - - if (typeof ret !== 'number') { - this.execute = this._executeError; - this._err = ret; - return ret; - } else if (ret === 0) { - this._seenCR = false; - this._buf = ''; - this._flags |= FLAG_TRAILING; - this._state = STATE_HEADER; - this.execute = this._executeHeader; - if (offset < len) { - ret = this.execute(data.slice(offset)); - if (typeof ret !== 'number') - return ret; - return offset + ret; - } - return offset; - } else { - nbytes = ret; - this._state = STATE_BODY_CHUNKED_BYTES; - continue; - } - } else { - // Check for possible cross-chunk CRLF split - var end; - if (data[len - 1] === CR) { - seenCR = true; - end = len - 1; + while (offset < len) { + var ch = data[offset]; + dec = hexValue(ch); + if (dec === undefined) { + state = STATE_BODY_CHUNKED_SIZE_IGNORE; + break; + } else if (nbytes === null) + nbytes = dec; + else { + nbytes *= 16; + nbytes += dec; } - buf += data.toString('ascii', offset, end); - offset = len; // break out of while loop + if (nbytes > MAX_CHUNK_SIZE) + return this._setError('Chunk size limit exceeded'); + ++offset; } break; case STATE_BODY_CHUNKED_BYTES: @@ -714,28 +539,60 @@ HTTPParser.prototype._executeBodyChunked = function(data) { this.onBody(data, offset, nbytes); offset += nbytes; nbytes = 0; - this._state = STATE_BODY_CHUNKED_BYTES_CRLF; + state = STATE_BODY_CHUNKED_BYTES_LF; } else { nbytes -= dataleft; this.onBody(data, offset, dataleft); offset = len; } break; - case STATE_BODY_CHUNKED_BYTES_CRLF: - if (nbytes === 0 && data[offset++] === CR) - ++nbytes; - else if (nbytes === 1 && data[offset++] === LF) - this._state = STATE_BODY_CHUNKED_SIZE; - else - return this._setError('Malformed chunk (missing CRLF)'); + case STATE_BODY_CHUNKED_BYTES_LF: + while (offset < len) { + var curByte = data[offset++]; + if (nbytes === 0) { + if (curByte === LF) { + state = STATE_BODY_CHUNKED_SIZE; + nbytes = null; + break; + } else if (curByte === CR) + ++nbytes; + } else if (nbytes === 1 && curByte === LF) { + state = STATE_BODY_CHUNKED_SIZE; + nbytes = null; + break; + } else + return this._setError('Malformed chunk (malformed line ending)'); + } + break; + case STATE_BODY_CHUNKED_SIZE_IGNORE: + // We only reach this state once we receive a non-hex character + while (offset < len) { + if (data[offset++] === LF) { + if (nbytes === 0) { + this._flags |= FLAG_TRAILING; + this._state = STATE_HEADER; + this._nbytes = null; + this.execute = this._executeHeader; + if (offset < len) { + var ret = this.execute(data.slice(offset)); + if (typeof ret !== 'number') + return ret; + return offset + ret; + } + return offset; + } else { + state = STATE_BODY_CHUNKED_BYTES; + break; + } + } + } break; default: return this._setError('Unexpected parser state while reading chunks'); } } - this._buf = buf; - this._seenCR = seenCR; + this._state = state; this._nbytes = nbytes; return len; @@ -800,7 +657,7 @@ HTTPParser.prototype._needsEOF = function(flags, type, status) { // See RFC 2616 section 4.4 if (status === 204 || // No Content status === 304 || // Not Modified - parseInt(status / 100, 10) === 1 || // 1xx e.g. Continue + (status >= 100 && status < 200) || // 1xx e.g. Continue (flags & FLAG_SKIPBODY) > 0) { // response to a HEAD request return false; } @@ -818,65 +675,61 @@ var REQUEST = HTTPParser.REQUEST = 0; HTTPParser.RESPONSE = 1; module.exports = HTTPParser; -function indexOfCRLF(buf, buflen, offset) { - var bo1; +function indexOfLF(buf, buflen, offset) { while (offset < buflen) { - bo1 = buf[offset + 1]; - if (buf[offset] === CR && bo1 === LF) + if (buf[offset] === LF) return offset; - else if (bo1 === CR) - ++offset; - else - offset += 2; + ++offset; } return -1; } -function readChunkSize(str) { - var val, dec; - for (var i = 0; i < str.length; ++i) { - dec = UNHEX[str.charCodeAt(i)]; - if (dec === undefined) - break; - else if (val === undefined) - val = dec; - else { - val *= 16; - val += dec; - } - } - if (val === undefined) - val = new Error('Invalid chunk size'); - else if (val > MAX_CHUNK_SIZE) - val = new Error('Chunk size too big'); - return val; +function hexValue(ch) { + if (ch > 47 && ch < 58) + return ch - 48; + ch |= 0x20; + if (ch > 96 && ch < 123) + return 10 + (ch - 97); } function ltrim(value) { var length = value.length, start; for (start = 0; start < length && - (value.charCodeAt(start) === 32 || value.charCodeAt(start) === 9); + (value.charCodeAt(start) === 32 || value.charCodeAt(start) === 9 || + value.charCodeAt(start) === CR); ++start); - return start > 0 ? value.slice(start) : value; + return (start > 0 ? value.slice(start) : value); } + +function rtrim(value) { + var length = value.length, end; + for (end = length; + end > 0 && + (value.charCodeAt(end - 1) === 32 || value.charCodeAt(end - 1) === 9 || + value.charCodeAt(end - 1) === CR); + --end); + return (end < length ? value.slice(0, end) : value); +} + function trim(value) { var length = value.length, start, end; for (start = 0; start < length && - (value.charCodeAt(start) === 32 || value.charCodeAt(start) === 9); + (value.charCodeAt(start) === 32 || value.charCodeAt(start) === 9 || + value.charCodeAt(start) === CR); ++start); for (end = length; end > start && - (value.charCodeAt(end - 1) === 32 || value.charCodeAt(end - 1) === 9); + (value.charCodeAt(end - 1) === 32 || value.charCodeAt(end - 1) === 9 || + value.charCodeAt(end - 1) === CR); --end); - return start > 0 || end < length ? value.slice(start, end) : value; + return (start > 0 || end < length ? value.slice(start, end) : value); } function equalsLower(input, ref) { var inlen = input.length; - var reflen = ref.length; - if (inlen !== reflen) + if (inlen !== ref.length) return false; for (var i = 0; i < inlen; ++i) { if ((input.charCodeAt(i) | 0x20) !== ref[i]) From b7ee346942d4e63bee16c96b14368e12cd646469 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 22 Apr 2015 14:55:30 -0400 Subject: [PATCH 56/67] http: fix comment style --- lib/_http_parser.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index ffc9902d798c2d..95229750fc7163 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -97,7 +97,7 @@ function HTTPParser(type) { this.onBody = null; this.onComplete = null; - // extra stuff tagged onto parser object by core modules/files + // Extra stuff tagged onto parser object by core modules/files this.onIncoming = null; this.incoming = null; this.socket = null; @@ -120,17 +120,17 @@ HTTPParser.prototype.reinitialize = function(type) { this._nhdrpairs = 0; this._buf = ''; - // common properties + // Common properties this.headers = []; this.httpMajor = 1; this.httpMinor = null; this.maxHeaderPairs = 2000; - // request properties + // Request properties this.method = null; this.url = null; - // response properties + // Response properties this.statusCode = null; this.statusText = null; }; From d97942b08e6aea39360fa083f02c46f841952fed Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 22 Apr 2015 14:56:53 -0400 Subject: [PATCH 57/67] http: fix bad chunk size detection --- lib/_http_parser.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 95229750fc7163..ae04d78547f04a 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -520,6 +520,8 @@ HTTPParser.prototype._executeBodyChunked = function(data) { var ch = data[offset]; dec = hexValue(ch); if (dec === undefined) { + if (nbytes === null) + return this._setError('Invalid chunk size'); state = STATE_BODY_CHUNKED_SIZE_IGNORE; break; } else if (nbytes === null) @@ -547,6 +549,8 @@ HTTPParser.prototype._executeBodyChunked = function(data) { } break; case STATE_BODY_CHUNKED_BYTES_LF: + // We reach this state after all chunk bytes have been read and we are + // looking for the (CR)LF following the bytes while (offset < len) { var curByte = data[offset++]; if (nbytes === 0) { @@ -565,7 +569,8 @@ HTTPParser.prototype._executeBodyChunked = function(data) { } break; case STATE_BODY_CHUNKED_SIZE_IGNORE: - // We only reach this state once we receive a non-hex character + // We only reach this state once we receive a non-hex character on the + // chunk size line while (offset < len) { if (data[offset++] === LF) { if (nbytes === 0) { @@ -688,7 +693,7 @@ function hexValue(ch) { if (ch > 47 && ch < 58) return ch - 48; ch |= 0x20; - if (ch > 96 && ch < 123) + if (ch > 96 && ch < 103) return 10 + (ch - 97); } From 79e2d60671d4bb2dcb71741ae796960a12588fd5 Mon Sep 17 00:00:00 2001 From: Brian White Date: Thu, 23 Apr 2015 16:40:12 -0400 Subject: [PATCH 58/67] http: avoid double request method traversal --- lib/_http_parser.js | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index ae04d78547f04a..5e22b023355cc8 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -90,6 +90,7 @@ var FLAG_TRAILING = 1 << 4; var FLAG_UPGRADE = 1 << 5; var FLAG_SKIPBODY = 1 << 6; var FLAG_SHOULD_KEEP_ALIVE = 1 << 7; +var FLAG_CONNECT_METHOD = 1 << 8; var FLAG_ANY_UPGRADE = FLAG_UPGRADE | FLAG_CONNECTION_UPGRADE; function HTTPParser(type) { @@ -161,7 +162,7 @@ HTTPParser.prototype._headersEnd = function() { var type = this.type; var method = this.method; var upgrade = ((flags & FLAG_ANY_UPGRADE) === FLAG_ANY_UPGRADE || - (type === REQUEST && equalsLower(method, CC_CONNECT))); + (flags & FLAG_CONNECT_METHOD) > 0); var contentLen = this._contentLen; var httpMajor = this.httpMajor; var httpMinor = this.httpMinor; @@ -416,6 +417,8 @@ HTTPParser.prototype._executeHeader = function(data) { // Now start working forwards and both validate the HTTP method and // find the start of the URL (in case there are multiple spaces/tabs // separating the method and the URL + var isConnect = false; + var c = 0; for (var i = 0; i < urlEnd; ++i) { ch = buf.charCodeAt(i); if (firstSP !== undefined) { @@ -423,10 +426,22 @@ HTTPParser.prototype._executeHeader = function(data) { urlStart = i; break; } - } else if (ch === 32) + } else if (ch === 32) { firstSP = i; - else if (ch < 33 || ch > 126) + isConnect = (c === 7); + } else if (ch < 33 || ch > 126) return this._setError('Malformed request line'); + else if (c >= 0) { + if (c === 7) + c = -1; + else { + ch |= 0x20; + if (ch !== CC_CONNECT[c]) + c = -1; + else + ++c; + } + } } if (firstSP === undefined || urlStart === undefined || @@ -434,6 +449,8 @@ HTTPParser.prototype._executeHeader = function(data) { return this._setError('Malformed request line'); } + if (isConnect) + this._flags |= FLAG_CONNECT_METHOD; this.httpMinor = minor; this.method = buf.slice(0, firstSP); this.url = buf.slice(urlStart, urlEnd); From 526b24f7d2a1e14b34e01832e25dfc0d0dfef6f6 Mon Sep 17 00:00:00 2001 From: Brian White Date: Thu, 23 Apr 2015 21:23:48 -0400 Subject: [PATCH 59/67] http: validate request url --- lib/_http_parser.js | 116 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 5e22b023355cc8..6bf07b6260ee93 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -449,11 +449,16 @@ HTTPParser.prototype._executeHeader = function(data) { return this._setError('Malformed request line'); } + var url = buf.slice(urlStart, urlEnd); + if (!validateUrl(url, isConnect)) + return this._setError('Malformed request line'); + if (isConnect) this._flags |= FLAG_CONNECT_METHOD; this.httpMinor = minor; this.method = buf.slice(0, firstSP); - this.url = buf.slice(urlStart, urlEnd); + this.url = url; + state = STATE_HEADER; break; case STATE_STATUS_LINE: @@ -760,6 +765,115 @@ function equalsLower(input, ref) { return true; } +function validateUrl(url, isConnect) { + var state = (isConnect ? 5 : 0); + var ch; + for (var i = 0; i < url.length; ++i) { + ch = url.charCodeAt(i); + switch (state) { + case 0: + // Proxied requests are followed by scheme of an absolute URI (alpha). + // All methods except CONNECT are followed by '/' or '*'. + if (ch === 47 || // '/' + ch === 42) { // '*' + state = 6; + continue; + } else if ((ch |= 0x20) > 96 && ch < 123) { // A-Za-z + state = 1; + continue; + } + break; + case 1: + if (ch === 58) { // ':' + state = 2; + continue; + } else if ((ch |= 0x20) > 96 && ch < 123) // A-Za-z + continue; + break; + case 2: + if (ch === 47) { // '/' + state = 3; + continue; + } + break; + case 3: + if (ch === 47) { // '/' + state = 5; + continue; + } + break; + case 4: + if (ch === 64) // '@' + return false; + // FALLTHROUGH + case 5: + if (ch === 47) { // '/' + state = 6; + continue; + } else if (ch === 63) { // '?' + state = 7; + continue; + } else if (ch === 64) { // '@' + state = 4; + continue; + } else if ((ch > 35 && ch < 58) || // 0-9 + ch === 33 || // '!' + ch === 58 || // ':' + ch === 59 || // ';' + ch === 61 || // '=' + ch === 91 || // '[' + ch === 93 || // ']' + ch === 95 || // '_' + ch === 126 || // '~' + ((ch |= 0x20) > 96 && ch < 123)) { // A-Za-z + continue; + } + break; + case 6: + if (ch === 63) { // '?' + state = 7; + continue; + } else if (ch === 35) { // '#' + state = 8; + continue; + // http://jsperf.com/check-url-character + } else if (!(ch < 33 || ch === 127)) // Normal URL characters + continue; + break; + case 7: + if (ch === 63) { // '?' + // Allow extra '?' in query string + continue; + } else if (ch === 35) { // '#' + state = 8; + continue; + } else if (!(ch < 33 || ch === 127)) // Normal URL characters + continue; + break; + case 8: + if (ch === 63) { // '?' + state = 9; + continue; + } else if (ch === 35) // '#' + continue; + else if (!(ch < 33 || ch === 127)) { // Normal URL characters + state = 9; + continue; + } + break; + case 9: + if (ch === 63 || // '?' + ch === 35 || // '#' + !(ch < 33 || ch === 127)) { // Normal URL characters + continue; + } + break; + } + return false; + } + return true; +} + function getFirstCharCode(str) { return str.charCodeAt(0); } From 950582a845a5566e8b0264fca5e6a7bf23fbf5dc Mon Sep 17 00:00:00 2001 From: Brian White Date: Thu, 23 Apr 2015 22:25:16 -0400 Subject: [PATCH 60/67] test: fix typo --- test/pummel/test-http-parser-durability.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pummel/test-http-parser-durability.js b/test/pummel/test-http-parser-durability.js index 289ef30faf8741..a7b9a9f7360514 100644 --- a/test/pummel/test-http-parser-durability.js +++ b/test/pummel/test-http-parser-durability.js @@ -1602,7 +1602,7 @@ cases.forEach(function(testCase) { statusCode, statusText, upgrade, shouldKeepAlive) { assert.strictEqual(reqEvents[0], 'onHeaders', - 'Expected onHeaders to the next event for: ' + + 'Expected onHeaders to be the next event for: ' + testCase.name); reqEvents.shift(); message = { From 3a870f1c412ed0661108ee037e46427e34451d5e Mon Sep 17 00:00:00 2001 From: Brian White Date: Thu, 23 Apr 2015 22:26:37 -0400 Subject: [PATCH 61/67] test: port URL tests from joyent/http-parser This only tests basic URL validation because the JS HTTP parser does not actually parse the URL into its individual parts. --- test/parallel/test-http-parser-url.js | 297 ++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 test/parallel/test-http-parser-url.js diff --git a/test/parallel/test-http-parser-url.js b/test/parallel/test-http-parser-url.js new file mode 100644 index 00000000000000..f6ba0dc331aa1a --- /dev/null +++ b/test/parallel/test-http-parser-url.js @@ -0,0 +1,297 @@ +var assert = require('assert'); + +var HTTPParser = require('_http_parser'); + +var REQUEST = HTTPParser.REQUEST; + +var cases = [ + { + name: 'proxy request', + url: 'http://hostname/' + }, + { + name: 'proxy request with port', + url: 'http://hostname:444/' + }, + { + name: 'CONNECT request', + url: 'hostname:443', + method: 'CONNECT' + }, + { + name: 'CONNECT request but not connect', + url: 'hostname:443', + error: true + }, + { + name: 'proxy ipv6 request', + url: 'http://[1:2::3:4]/' + }, + { + name: 'proxy ipv6 request with port', + url: 'http://[1:2::3:4]:67/' + }, + { + name: 'CONNECT ipv6 address', + url: '[1:2::3:4]:443', + method: 'CONNECT' + }, + { + name: 'ipv4 in ipv6 address', + url: 'http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/' + }, + { + name: 'extra ? in query string', + url: 'http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css,' + + 'fp-channel-min.css,fp-product-min.css,fp-mall-min.css,' + + 'fp-category-min.css,fp-sub-min.css,fp-gdp4p-min.css,' + + 'fp-css3-min.css,fp-misc-min.css?t=20101022.css' + }, + { + name: 'space URL encoded', + url: '/toto.html?toto=a%20b' + }, + { + name: 'URL fragment', + url: '/toto.html#titi' + }, + { + name: 'complex URL fragment', + url: 'http://www.webmasterworld.com/r.cgi?f=21&d=8405&url=' + + 'http://www.example.com/index.html?foo=bar&hello=world#midpage' + }, + { + name: 'complex URL from node js url parser doc', + url: 'http://host.com:8080/p/a/t/h?query=string#hash' + }, + { + name: 'complex URL with basic auth from node js url parser doc', + url: 'http://a:b@host.com:8080/p/a/t/h?query=string#hash' + }, + { + name: 'double @', + url: 'http://a:b@@hostname:443/', + error: true + }, + { + name: 'proxy empty host', + url: 'http://:443/', + // Commented out because the JavaScript HTTP parser only performs basic + // character validation and does not actually parse the URL into its + // separate parts + //error: true + }, + { + name: 'proxy empty port', + url: 'http://hostname:/', + // Commented out because the JavaScript HTTP parser only performs basic + // character validation and does not actually parse the URL into its + // separate parts + //error: true + }, + { + name: 'CONNECT with basic auth', + url: 'a:b@hostname:443', + method: 'CONNECT', + // Commented out because the JavaScript HTTP parser only performs basic + // character validation and does not actually parse the URL into its + // separate parts + //error: true + }, + { + name: 'CONNECT empty host', + url: ':443', + method: 'CONNECT', + // Commented out because the JavaScript HTTP parser only performs basic + // character validation and does not actually parse the URL into its + // separate parts + //error: true + }, + { + name: 'CONNECT empty port', + url: 'hostname:', + method: 'CONNECT', + // Commented out because the JavaScript HTTP parser only performs basic + // character validation and does not actually parse the URL into its + // separate parts + //error: true + }, + { + name: 'CONNECT with extra bits', + url: 'hostname:443/', + method: 'CONNECT', + // Commented out because the JavaScript HTTP parser only performs basic + // character validation and does not actually parse the URL into its + // separate parts + //error: true + }, + { + name: 'space in URL', + url: '/foo bar/', + error: true + }, + { + name: 'proxy basic auth with space url encoded', + url: 'http://a%20:b@host.com/' + }, + { + name: 'carriage return in URL', + url: '/foo\rbar/', + error: true + }, + { + name: 'proxy double : in URL', + url: 'http://hostname::443/', + // Commented out because the JavaScript HTTP parser only performs basic + // character validation and does not actually parse the URL into its + // separate parts + //error: true + }, + { + name: 'proxy basic auth with double :', + url: 'http://a::b@host.com/' + }, + { + name: 'line feed in URL', + url: '/foo\nbar/', + error: true + }, + { + name: 'proxy empty basic auth', + url: 'http://@hostname/fo' + }, + { + name: 'proxy line feed in hostname', + url: 'http://host\name/fo', + error: true + }, + { + name: 'proxy % in hostname', + url: 'http://host%name/fo', + // Commented out because the JavaScript HTTP parser only performs basic + // character validation and does not actually parse the URL into its + // separate parts + //error: true + }, + { + name: 'proxy ; in hostname', + url: 'http://host;ame/fo', + // Commented out because the JavaScript HTTP parser only performs basic + // character validation and does not actually parse the URL into its + // separate parts + //error: true + }, + { + name: 'proxy basic auth with unreservedchars', + url: 'http://a!;-_!=+$@host.com/' + }, + { + name: 'proxy only empty basic auth', + url: 'http://@/fo', + // Commented out because the JavaScript HTTP parser only performs basic + // character validation and does not actually parse the URL into its + // separate parts + //error: true + }, + { + name: 'proxy only basic auth', + url: 'http://toto@/fo', + // Commented out because the JavaScript HTTP parser only performs basic + // character validation and does not actually parse the URL into its + // separate parts + //error: true + }, + { + name: 'proxy empty hostname', + url: 'http:///fo', + // Commented out because the JavaScript HTTP parser only performs basic + // character validation and does not actually parse the URL into its + // separate parts + //error: true + }, + { + name: 'proxy = in URL', + url: 'http://host=ame/fo', + // Commented out because the JavaScript HTTP parser only performs basic + // character validation and does not actually parse the URL into its + // separate parts + //error: true + }, + { + name: 'tab in URL', + url: '/foo\tbar/', + error: true + }, + { + name: 'form feed in URL', + url: '/foo\fbar/', + error: true + }, +]; + + +// Prevent EE warnings since we have many test cases which attach `exit` event +// handlers +process.setMaxListeners(0); + +// Test predefined urls +cases.forEach(function(testCase) { + var parser = new HTTPParser(REQUEST); + var method = (testCase.method || 'GET'); + var url = testCase.url; + var input = new Buffer(method + ' ' + url + ' HTTP/1.0\r\n\r\n', 'binary'); + var sawHeaders = false; + var completed = false; + + function onHeaders(versionMajor, versionMinor, headers, method_, url_, + statusCode, statusText, upgrade, shouldKeepAlive) { + sawHeaders = true; + assert.strictEqual(versionMajor, 1); + assert.strictEqual(versionMinor, 0); + assert.deepEqual(headers, []); + assert.strictEqual(method_, method); + assert.strictEqual(url_, url); + assert.strictEqual(statusCode, null); + assert.strictEqual(statusText, null); + assert.strictEqual(upgrade, method === 'CONNECT'); + assert.strictEqual(shouldKeepAlive, false); + } + + function onBody(data, offset, len) { + assert('Unexpected body'); + } + + function onComplete() { + assert.strictEqual(sawHeaders, true); + completed = true; + } + + parser.onHeaders = onHeaders; + parser.onBody = onBody; + parser.onComplete = onComplete; + + process.on('exit', function() { + assert.strictEqual(completed, + true, + 'Parsing did not complete for: ' + testCase.name); + }); + + var ret; + try { + ret = parser.execute(input); + parser.finish(); + } catch (ex) { + throw new Error('Unexpected error thrown for: ' + testCase.name + ':\n\n' + + ex.stack + '\n'); + } + if (testCase.error !== undefined && typeof ret === 'number') + throw new Error('Expected error for: ' + testCase.name); + else if (testCase.error === undefined && typeof ret !== 'number') { + throw new Error('Unexpected error for: ' + testCase.name + ':\n\n' + + ret.stack + '\n'); + } + if (testCase.error !== undefined) { + completed = true; // Prevent error from throwing on script exit + return; + } +}); From d3247f198841f7c30daa9b9c442c540f8880e04c Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 24 Apr 2015 22:13:22 -0400 Subject: [PATCH 62/67] http: move property initialization to prototype This bumps the large-alternate parser benchmark to ~19k+, making it match the result from joyent/http-parser 2.5.0. --- lib/_http_parser.js | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 6bf07b6260ee93..424f632fcf0005 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -39,6 +39,9 @@ Misc differences with joyent/http-parser: var inspect = require('util').inspect; +var REQUEST = HTTPParser.REQUEST = 0; +var RESPONSE = HTTPParser.RESPONSE = 1; + var CR = 13; var LF = 10; @@ -94,17 +97,37 @@ var FLAG_CONNECT_METHOD = 1 << 8; var FLAG_ANY_UPGRADE = FLAG_UPGRADE | FLAG_CONNECTION_UPGRADE; function HTTPParser(type) { - this.onHeaders = null; - this.onBody = null; - this.onComplete = null; - - // Extra stuff tagged onto parser object by core modules/files - this.onIncoming = null; - this.incoming = null; - this.socket = null; - - this.reinitialize(type); + if (type === RESPONSE) { + this.type = type; + this._state = STATE_STATUS_LINE; + } + this.headers = []; } +HTTPParser.prototype.type = REQUEST; +HTTPParser.prototype._state = STATE_REQ_LINE; +HTTPParser.prototype._err = null; +HTTPParser.prototype._flags = 0; +HTTPParser.prototype._contentLen = null; +HTTPParser.prototype._nbytes = null; +HTTPParser.prototype._nhdrbytes = 0; +HTTPParser.prototype._nhdrpairs = 0; +HTTPParser.prototype._buf = ''; +HTTPParser.prototype.httpMajor = 1; +HTTPParser.prototype.httpMinor = null; +HTTPParser.prototype.maxHeaderPairs = 2000; +HTTPParser.prototype.method = null; +HTTPParser.prototype.url = null; +HTTPParser.prototype.statusCode = null; +HTTPParser.prototype.statusText = null; + +HTTPParser.prototype.onHeaders = null; +HTTPParser.prototype.onBody = null; +HTTPParser.prototype.onComplete = null; + +HTTPParser.prototype.onIncoming = null; +HTTPParser.prototype.incoming = null; +HTTPParser.prototype.socket = null; + HTTPParser.prototype.reinitialize = function(type) { this.execute = this._executeStartLine; this.type = type; @@ -698,8 +721,6 @@ HTTPParser.prototype._needsEOF = function(flags, type, status) { -var REQUEST = HTTPParser.REQUEST = 0; -HTTPParser.RESPONSE = 1; module.exports = HTTPParser; function indexOfLF(buf, buflen, offset) { From fb32e800df3d310e718247eaacbdb7090170ef98 Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 24 Apr 2015 22:13:45 -0400 Subject: [PATCH 63/67] http: check valid field name before checking range --- lib/_http_parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 424f632fcf0005..b350990664c5b2 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -321,7 +321,7 @@ HTTPParser.prototype._executeHeader = function(data) { if (i === 0 || !validFieldName) return this._setError('Malformed header line'); idx = i; - } else if (ch < 33 || ch > 126) + } else if (validFieldName && (ch < 33 || ch > 126)) validFieldName = false; } else if (ch !== 32 && ch !== 9) { valueStart = i; From 4af63d708286d19c77ad0c34061d246142acd926 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 26 Apr 2015 01:39:41 -0400 Subject: [PATCH 64/67] http: switch to regexp search for longer field names Somewhere between 10 and 14 characters, the equalsLower() algorithm becomes slower than searching with a simple, case-insensitive, non-anchored regexp. This at least gives a consistently higher bump in the large-alternate parser benchmark. --- lib/_http_parser.js | 97 +++++++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index b350990664c5b2..6e5cf4d8fb8ffb 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -66,10 +66,10 @@ var RE_CONN_KEEPALIVE = /(?:^|[\t ,]+)keep\-alive(?:\r?$|[\t ,]+)/i; var RE_CONN_UPGRADE = /(?:^|[\t ,]+)upgrade(?:\r?$|[\t ,]+)/i; var RE_TE_CHUNKED = /(?:^|[\t ,]+)chunked(?:\r?$|[\t ,]+)/i; var CC_CONNECT = 'connect'.split('').map(getFirstCharCode); -var CC_CONNECTION = 'connection'.split('').map(getFirstCharCode); -var CC_TE = 'transfer-encoding'.split('').map(getFirstCharCode); var CC_UPGRADE = 'upgrade'.split('').map(getFirstCharCode); -var CC_CONTLEN = 'content-length'.split('').map(getFirstCharCode); +var CC_CONNECTION = 'connection'.split('').map(getFirstCharCode); +var RE_CONTLEN = /Content\-Length/i; +var RE_TE = /Transfer\-Encoding/i; var REQ_HTTP_VER_BYTES = ' HTTP/1.'.split('').map(getFirstCharCode); var RES_HTTP_VER_BYTES = REQ_HTTP_VER_BYTES.slice(1); REQ_HTTP_VER_BYTES.reverse(); @@ -348,26 +348,8 @@ HTTPParser.prototype._executeHeader = function(data) { fieldValue = extra; } else fieldValue = extra; - // Need to re-check value since matched values may now exist ... - if (equalsLower(fieldName, CC_CONNECTION)) { - if (fieldValue.search(RE_CONN_CLOSE) > -1) - this._flags |= FLAG_CONNECTION_CLOSE; - if (fieldValue.search(RE_CONN_KEEPALIVE) > -1) - this._flags |= FLAG_CONNECTION_KEEP_ALIVE; - if (fieldValue.search(RE_CONN_UPGRADE) > -1) - this._flags |= FLAG_CONNECTION_UPGRADE; - } else if (equalsLower(fieldName, CC_TE)) { - if (fieldValue.search(RE_TE_CHUNKED) > -1) - this._flags |= FLAG_CHUNKED; - } else if (equalsLower(fieldName, CC_UPGRADE)) { - this._flags |= FLAG_UPGRADE; - } else if (equalsLower(fieldName, CC_CONTLEN)) { - var val = parseInt(fieldValue, 10); - if (val !== val || val > MAX_CHUNK_SIZE) - return this._setError('Bad Content-Length: ' + inspect(val)); - this._contentLen = val; - } headers[headerslen - 1] = fieldValue; + idx = fieldName.length; } } else { fieldName = buf.slice(0, idx); @@ -376,29 +358,53 @@ HTTPParser.prototype._executeHeader = function(data) { // header values gets trimmed if (headerslen > 0) headers[headerslen - 1] = rtrim(headers[headerslen - 1]); - if (equalsLower(fieldName, CC_CONNECTION)) { - if (fieldValue.search(RE_CONN_CLOSE) > -1) - this._flags |= FLAG_CONNECTION_CLOSE; - if (fieldValue.search(RE_CONN_KEEPALIVE) > -1) - this._flags |= FLAG_CONNECTION_KEEP_ALIVE; - if (fieldValue.search(RE_CONN_UPGRADE) > -1) - this._flags |= FLAG_CONNECTION_UPGRADE; - } else if (equalsLower(fieldName, CC_TE)) { - if (fieldValue.search(RE_TE_CHUNKED) > -1) - this._flags |= FLAG_CHUNKED; - } else if (equalsLower(fieldName, CC_UPGRADE)) { - this._flags |= FLAG_UPGRADE; - } else if (equalsLower(fieldName, CC_CONTLEN)) { - var val = parseInt(fieldValue, 10); - if (val !== val || val > MAX_CHUNK_SIZE) - return this._setError('Bad Content-Length: ' + inspect(val)); - this._contentLen = val; - } if (maxHeaderPairs <= 0 || ++this._nhdrpairs < maxHeaderPairs) { headers.push(fieldName, fieldValue); headerslen += 2; } } + switch (idx) { + case 7: + for (var i = 0; i < 7; ++i) { + if ((fieldName.charCodeAt(i) | 0x20) !== CC_UPGRADE[i]) + break; + } + if (i === 7) + this._flags |= FLAG_UPGRADE; + break; + case 10: + for (var i = 0; i < 10; ++i) { + if ((fieldName.charCodeAt(i) | 0x20) !== CC_CONNECTION[i]) + break; + } + if (i === 10) { + if (fieldValue.search(RE_CONN_CLOSE) > -1) + this._flags |= FLAG_CONNECTION_CLOSE; + if (fieldValue.search(RE_CONN_KEEPALIVE) > -1) + this._flags |= FLAG_CONNECTION_KEEP_ALIVE; + if (fieldValue.search(RE_CONN_UPGRADE) > -1) + this._flags |= FLAG_CONNECTION_UPGRADE; + } + break; + case 14: + // Somewhere between 10 and 14 characters, the previously used + // for loop algorithm becomes slower than doing a search using a + // simple, case-insensitive, non-anchored regexp + if (fieldValue.length > 0 && + fieldName.search(RE_CONTLEN) === 0) { + var val = parseInt(fieldValue, 10); + if (val !== val || val > MAX_CHUNK_SIZE) + return this._setError('Bad Content-Length'); + this._contentLen = val; + } + break; + case 17: + if (fieldName.search(RE_TE) === 0 && + fieldValue.search(RE_TE_CHUNKED) > -1) { + this._flags |= FLAG_CHUNKED; + } + break; + } break; case STATE_REQ_LINE: // Original HTTP parser ignored blank lines before request/status @@ -775,17 +781,6 @@ function trim(value) { return (start > 0 || end < length ? value.slice(start, end) : value); } -function equalsLower(input, ref) { - var inlen = input.length; - if (inlen !== ref.length) - return false; - for (var i = 0; i < inlen; ++i) { - if ((input.charCodeAt(i) | 0x20) !== ref[i]) - return false; - } - return true; -} - function validateUrl(url, isConnect) { var state = (isConnect ? 5 : 0); var ch; From 7ba6c2b0db90b48a82de9c846fec56606223f428 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 26 Apr 2015 22:45:53 -0400 Subject: [PATCH 65/67] http: remove parser pause()/resume() usage The js parser pause()/resume() are no-ops anyway. --- lib/_http_server.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/_http_server.js b/lib/_http_server.js index 23c7c125e2352a..9a4e053095517b 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -352,12 +352,6 @@ function connectionListener(socket) { socket.destroy(); } } - - if (socket._paused) { - // onIncoming paused the socket, we should pause the parser as well - debug('pause parser'); - socket.parser.pause(); - } } function socketOnEnd() { @@ -392,7 +386,6 @@ function connectionListener(socket) { // If we previously paused, then start reading again. if (socket._paused) { socket._paused = false; - socket.parser.resume(); socket.resume(); } } From 3da44278817274030abb42c75ef2a6d106680b3a Mon Sep 17 00:00:00 2001 From: Brian White Date: Mon, 27 Apr 2015 21:53:56 -0400 Subject: [PATCH 66/67] test: update comment --- test/parallel/test-http-parser.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/parallel/test-http-parser.js b/test/parallel/test-http-parser.js index 659f047bdd3c4f..12d856d4313052 100644 --- a/test/parallel/test-http-parser.js +++ b/test/parallel/test-http-parser.js @@ -1,5 +1,4 @@ 'use strict'; -var common = require('../common'); var assert = require('assert'); var HTTPParser = require('_http_parser'); @@ -10,7 +9,7 @@ var RESPONSE = HTTPParser.RESPONSE; // The purpose of this test is not to check HTTP compliance but to test the // binding. Tests for pathological http messages should be added to -// test-http-parser-durability.js +// pummel/test-http-parser-durability.js function newParser(type) { From d56a9f07b3b77465e460df23c13e9ce7dfab2d26 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 21 Jun 2015 01:51:08 -0400 Subject: [PATCH 67/67] lib,test: fix lint issues --- lib/_http_parser.js | 36 ++++++++++++++-------- test/parallel/test-http-parser-url.js | 2 ++ test/pummel/test-http-parser-durability.js | 12 +++++--- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/lib/_http_parser.js b/lib/_http_parser.js index 6e5cf4d8fb8ffb..452e580c94bdee 100644 --- a/lib/_http_parser.js +++ b/lib/_http_parser.js @@ -36,6 +36,7 @@ Misc differences with joyent/http-parser: can read a bit more about this in [RFC 7230 A.1.2](https://tools.ietf.org/html/rfc7230#page-79) */ +'use strict'; var inspect = require('util').inspect; @@ -285,8 +286,9 @@ HTTPParser.prototype._executeHeader = function(data) { else if (state === STATE_STATUS_LINE && nhdrbytes > HTTPParser.MAX_RES_LINE) { return this._setError('Response line limit exceeded'); - } else if (nhdrbytes > MAX_HEADER_BYTES) + } else if (nhdrbytes > MAX_HEADER_BYTES) { return this._setError('Header limit exceeded'); + } buf += data.toString('binary', offset, ret); } @@ -305,8 +307,9 @@ HTTPParser.prototype._executeHeader = function(data) { if (typeof ret !== 'number') return ret; return offset + ret; - } else if (state === STATE_COMPLETE) + } else if (state === STATE_COMPLETE) { this.reinitialize(this.type); + } return offset; } var idx = -1; @@ -321,8 +324,9 @@ HTTPParser.prototype._executeHeader = function(data) { if (i === 0 || !validFieldName) return this._setError('Malformed header line'); idx = i; - } else if (validFieldName && (ch < 33 || ch > 126)) + } else if (validFieldName && (ch < 33 || ch > 126)) { validFieldName = false; + } } else if (ch !== 32 && ch !== 9) { valueStart = i; break; @@ -346,8 +350,9 @@ HTTPParser.prototype._executeHeader = function(data) { fieldValue += ' ' + extra; else fieldValue = extra; - } else + } else { fieldValue = extra; + } headers[headerslen - 1] = fieldValue; idx = fieldName.length; } @@ -540,8 +545,9 @@ HTTPParser.prototype._executeHeader = function(data) { else if (state === STATE_STATUS_LINE && nhdrbytes > HTTPParser.MAX_RES_LINE) { return this._setError('Response line limit exceeded'); - } else if (nhdrbytes > MAX_HEADER_BYTES) + } else if (nhdrbytes > MAX_HEADER_BYTES) { return this._setError('Header limit exceeded'); + } buf += data.toString('binary', offset); break; } @@ -609,14 +615,16 @@ HTTPParser.prototype._executeBodyChunked = function(data) { state = STATE_BODY_CHUNKED_SIZE; nbytes = null; break; - } else if (curByte === CR) + } else if (curByte === CR) { ++nbytes; + } } else if (nbytes === 1 && curByte === LF) { state = STATE_BODY_CHUNKED_SIZE; nbytes = null; break; - } else + } else { return this._setError('Malformed chunk (malformed line ending)'); + } } break; case STATE_BODY_CHUNKED_SIZE_IGNORE: @@ -671,8 +679,9 @@ HTTPParser.prototype._executeBodyLiteral = function(data) { return ret; return nbytes + ret; } - } else + } else { this._state = STATE_COMPLETE; + } } else { this._contentLen -= len; this.onBody(data, 0, len); @@ -803,8 +812,9 @@ function validateUrl(url, isConnect) { if (ch === 58) { // ':' state = 2; continue; - } else if ((ch |= 0x20) > 96 && ch < 123) // A-Za-z + } else if ((ch |= 0x20) > 96 && ch < 123) { // A-Za-z continue; + } break; case 2: if (ch === 47) { // '/' @@ -821,7 +831,7 @@ function validateUrl(url, isConnect) { case 4: if (ch === 64) // '@' return false; - // FALLTHROUGH + // falls through case 5: if (ch === 47) { // '/' state = 6; @@ -853,8 +863,9 @@ function validateUrl(url, isConnect) { state = 8; continue; // http://jsperf.com/check-url-character - } else if (!(ch < 33 || ch === 127)) // Normal URL characters + } else if (!(ch < 33 || ch === 127)) { // Normal URL characters continue; + } break; case 7: if (ch === 63) { // '?' @@ -863,8 +874,9 @@ function validateUrl(url, isConnect) { } else if (ch === 35) { // '#' state = 8; continue; - } else if (!(ch < 33 || ch === 127)) // Normal URL characters + } else if (!(ch < 33 || ch === 127)) { // Normal URL characters continue; + } break; case 8: if (ch === 63) { // '?' diff --git a/test/parallel/test-http-parser-url.js b/test/parallel/test-http-parser-url.js index f6ba0dc331aa1a..2d7ee57129a264 100644 --- a/test/parallel/test-http-parser-url.js +++ b/test/parallel/test-http-parser-url.js @@ -1,3 +1,5 @@ +'use strict'; + var assert = require('assert'); var HTTPParser = require('_http_parser'); diff --git a/test/pummel/test-http-parser-durability.js b/test/pummel/test-http-parser-durability.js index a7b9a9f7360514..7bdc1b2150ecd5 100644 --- a/test/pummel/test-http-parser-durability.js +++ b/test/pummel/test-http-parser-durability.js @@ -1,3 +1,5 @@ +'use strict'; + var assert = require('assert'); var inspect = require('util').inspect; var format = require('util').format; @@ -1628,8 +1630,9 @@ cases.forEach(function(testCase) { testCase.name); reqEvents.shift(); message.body = data.toString('binary', offset, offset + len); - } else + } else { message.body += data.toString('binary', offset, offset + len); + } } function onComplete() { @@ -1787,8 +1790,8 @@ cases.forEach(function(testCase) { if (!cases[j].shouldKeepAlive || cases[j].error !== undefined) continue; for (var k = responsesStart; k < cases.length; ++k) { - if (cases[i].error !== undefined) - continue; + if (cases[i].error !== undefined) + continue; testMultiple3(cases[i], cases[j], cases[k]); } } @@ -2243,8 +2246,9 @@ function testMultiple3(case1, case2, case3) { if (hasUpgrade) { var lastMessage = messages.slice(-1)[0]; upgradeMessageFix(total, ret, lastMessage, case1, case2, case3); - } else + } else { assert.strictEqual(ret, total.length); + } assertMessageEquals(messages[0], case1); if (messages.length > 1)