deps: update http_parser to 2.5.0
authorFedor Indutny <fedor@indutny.com>
Fri, 24 Apr 2015 13:47:40 +0000 (15:47 +0200)
committerFedor Indutny <fedor@indutny.com>
Fri, 24 Apr 2015 21:19:28 +0000 (23:19 +0200)
PR-URL: https://github.com/iojs/io.js/pull/1517
Reviewed-By: Brian White <mscdex@mscdex.net>
14 files changed:
deps/http_parser/.gitignore
deps/http_parser/.mailmap
deps/http_parser/.travis.yml
deps/http_parser/AUTHORS
deps/http_parser/CONTRIBUTIONS [deleted file]
deps/http_parser/Makefile
deps/http_parser/README.md
deps/http_parser/bench.c [new file with mode: 0644]
deps/http_parser/contrib/parsertrace.c
deps/http_parser/contrib/url_parser.c
deps/http_parser/http_parser.c
deps/http_parser/http_parser.h
deps/http_parser/test.c
test/parallel/test-http-upgrade-yes-please.js [deleted file]

index 594f304..32cb51b 100644 (file)
@@ -5,6 +5,7 @@ tags
 test
 test_g
 test_fast
+bench
 url_parser
 parsertrace
 parsertrace_g
index 75a187c..278d141 100644 (file)
@@ -5,3 +5,4 @@ Salman Haq <salman.haq@asti-usa.com>
 Simon Zimmermann <simonz05@gmail.com>
 Thomas LE ROUX <thomas@november-eleven.fr> LE ROUX Thomas <thomas@procheo.fr>
 Thomas LE ROUX <thomas@november-eleven.fr> Thomas LE ROUX <thomas@procheo.fr>
+Fedor Indutny <fedor@indutny.com>
index ae85af0..4b038e6 100644 (file)
@@ -10,4 +10,4 @@ script:
 notifications:
   email: false
   irc:
-    - "irc.freenode.net#libuv"
+    - "irc.freenode.net#node-ci"
index 51b53b1..8e2df1d 100644 (file)
@@ -39,12 +39,29 @@ BogDan Vatra <bogdan@kde.org>
 Peter Faiman <peter@thepicard.org>
 Corey Richardson <corey@octayn.net>
 Tóth Tamás <tomika_nospam@freemail.hu>
-Patrik Stutz <patrik.stutz@gmail.com>
 Cam Swords <cam.swords@gmail.com>
 Chris Dickinson <christopher.s.dickinson@gmail.com>
 Uli Köhler <ukoehler@btronik.de>
 Charlie Somerville <charlie@charliesomerville.com>
+Patrik Stutz <patrik.stutz@gmail.com>
 Fedor Indutny <fedor.indutny@gmail.com>
 runner <runner.mei@gmail.com>
 Alexis Campailla <alexis@janeasystems.com>
 David Wragg <david@wragg.org>
+Vinnie Falco <vinnie.falco@gmail.com>
+Alex Butum <alexbutum@linux.com>
+Rex Feng <rexfeng@gmail.com>
+Alex Kocharin <alex@kocharin.ru>
+Mark Koopman <markmontymark@yahoo.com>
+Helge Heß <me@helgehess.eu>
+Alexis La Goutte <alexis.lagoutte@gmail.com>
+George Miroshnykov <george.miroshnykov@gmail.com>
+Maciej Małecki <me@mmalecki.com>
+Marc O'Morain <github.com@marcomorain.com>
+Jeff Pinner <jpinner@twitter.com>
+Timothy J Fontaine <tjfontaine@gmail.com>
+Akagi201 <akagi201@gmail.com>
+Romain Giraud <giraud.romain@gmail.com>
+Jay Satiro <raysatiro@yahoo.com>
+Arne Steen <Arne.Steen@gmx.de>
+Kjell Schubert <kjell.schubert@gmail.com>
diff --git a/deps/http_parser/CONTRIBUTIONS b/deps/http_parser/CONTRIBUTIONS
deleted file mode 100644 (file)
index 11ba31e..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-Contributors must agree to the Contributor License Agreement before patches
-can be accepted.
-
-http://spreadsheets2.google.com/viewform?hl=en&formkey=dDJXOGUwbzlYaWM4cHN1MERwQS1CSnc6MQ
index 3ce463b..373709c 100644 (file)
 # IN THE SOFTWARE.
 
 PLATFORM ?= $(shell sh -c 'uname -s | tr "[A-Z]" "[a-z]"')
-SONAME ?= libhttp_parser.so.2.3
+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)
@@ -61,6 +71,12 @@ test_fast: http_parser.o test.o http_parser.h
 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
 
@@ -94,6 +110,21 @@ parsertrace_g: http_parser_g.o contrib/parsertrace.c
 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.* \
@@ -102,4 +133,4 @@ clean:
 contrib/url_parser.c:  http_parser.h
 contrib/parsertrace.c: http_parser.h
 
-.PHONY: clean package test-run test-run-timed test-valgrind
+.PHONY: clean package test-run test-run-timed test-valgrind install install-strip uninstall
index 0bf5d35..7c54dd4 100644 (file)
@@ -61,7 +61,7 @@ if (recved < 0) {
 }
 
 /* Start up / continue the parser.
- * Note we pass recved==0 to signal that EOF has been recieved.
+ * Note we pass recved==0 to signal that EOF has been received.
  */
 nparsed = http_parser_execute(parser, &settings, buf, recved);
 
@@ -75,7 +75,7 @@ if (parser->upgrade) {
 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 forth parameter to `http_parser_execute()`. Callbacks and errors
+`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.
 
@@ -110,7 +110,7 @@ followed by non-HTTP data.
 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
+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
@@ -131,7 +131,7 @@ 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_uri,
+    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
@@ -145,7 +145,7 @@ 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 following logic:
+and apply the following logic:
 
     (on_header_field and on_header_value shortened to on_h_*)
      ------------------------ ------------ --------------------------------------------
diff --git a/deps/http_parser/bench.c b/deps/http_parser/bench.c
new file mode 100644 (file)
index 0000000..5b452fa
--- /dev/null
@@ -0,0 +1,111 @@
+/* Copyright Fedor Indutny. 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 <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+
+static const char data[] =
+    "POST /joyent/http-parser HTTP/1.1\r\n"
+    "Host: github.com\r\n"
+    "DNT: 1\r\n"
+    "Accept-Encoding: gzip, deflate, sdch\r\n"
+    "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n"
+    "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) "
+        "AppleWebKit/537.36 (KHTML, like Gecko) "
+        "Chrome/39.0.2171.65 Safari/537.36\r\n"
+    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,"
+        "image/webp,*/*;q=0.8\r\n"
+    "Referer: https://github.com/joyent/http-parser\r\n"
+    "Connection: keep-alive\r\n"
+    "Transfer-Encoding: chunked\r\n"
+    "Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n\r\n";
+static const size_t data_len = sizeof(data) - 1;
+
+static int on_info(http_parser* p) {
+  return 0;
+}
+
+
+static int on_data(http_parser* p, const char *at, size_t length) {
+  return 0;
+}
+
+static http_parser_settings settings = {
+  .on_message_begin = on_info,
+  .on_headers_complete = on_info,
+  .on_message_complete = on_info,
+  .on_header_field = on_data,
+  .on_header_value = on_data,
+  .on_url = on_data,
+  .on_status = on_data,
+  .on_body = on_data
+};
+
+int bench(int iter_count, int silent) {
+  struct http_parser parser;
+  int i;
+  int err;
+  struct timeval start;
+  struct timeval end;
+  float rps;
+
+  if (!silent) {
+    err = gettimeofday(&start, NULL);
+    assert(err == 0);
+  }
+
+  for (i = 0; i < iter_count; i++) {
+    size_t parsed;
+    http_parser_init(&parser, HTTP_REQUEST);
+
+    parsed = http_parser_execute(&parser, &settings, data, data_len);
+    assert(parsed == data_len);
+  }
+
+  if (!silent) {
+    err = gettimeofday(&end, NULL);
+    assert(err == 0);
+
+    fprintf(stdout, "Benchmark result:\n");
+
+    rps = (float) (end.tv_sec - start.tv_sec) +
+          (end.tv_usec - start.tv_usec) * 1e-6f;
+    fprintf(stdout, "Took %f seconds to run\n", rps);
+
+    rps = (float) iter_count / rps;
+    fprintf(stdout, "%f req/sec\n", rps);
+    fflush(stdout);
+  }
+
+  return 0;
+}
+
+int main(int argc, char** argv) {
+  if (argc == 2 && strcmp(argv[1], "infinite") == 0) {
+    for (;;)
+      bench(5000000, 1);
+    return 0;
+  } else {
+    return bench(5000000, 0);
+  }
+}
index c9bc71e..e715368 100644 (file)
@@ -111,14 +111,14 @@ int main(int argc, char* argv[]) {
   FILE* file = fopen(filename, "r");
   if (file == NULL) {
     perror("fopen");
-    return EXIT_FAILURE;
+    goto fail;
   }
 
   fseek(file, 0, SEEK_END);
   long file_length = ftell(file);
   if (file_length == -1) {
     perror("ftell");
-    return EXIT_FAILURE;
+    goto fail;
   }
   fseek(file, 0, SEEK_SET);
 
@@ -126,7 +126,7 @@ int main(int argc, char* argv[]) {
   if (fread(data, 1, file_length, file) != (size_t)file_length) {
     fprintf(stderr, "couldn't read entire file\n");
     free(data);
-    return EXIT_FAILURE;
+    goto fail;
   }
 
   http_parser_settings settings;
@@ -149,8 +149,12 @@ int main(int argc, char* argv[]) {
             "Error: %s (%s)\n",
             http_errno_description(HTTP_PARSER_ERRNO(&parser)),
             http_errno_name(HTTP_PARSER_ERRNO(&parser)));
-    return EXIT_FAILURE;
+    goto fail;
   }
 
   return EXIT_SUCCESS;
+
+fail:
+  fclose(file);
+  return EXIT_FAILURE;
 }
index b1f9c97..6650b41 100644 (file)
@@ -14,7 +14,7 @@ dump_url (const char *url, const struct http_parser_url *u)
       continue;
     }
 
-    printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n",
+    printf("\tfield_data[%u]: off: %u, len: %u, part: %.*s\n",
            i,
            u->field_data[i].off,
            u->field_data[i].len,
@@ -24,16 +24,18 @@ dump_url (const char *url, const struct http_parser_url *u)
 }
 
 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;
   }
-  struct http_parser_url u;
-  int len = strlen(argv[2]);
-  int connect = strcmp("connect", argv[1]) == 0 ? 1 : 0;
+  len = strlen(argv[2]);
+  connect = strcmp("connect", argv[1]) == 0 ? 1 : 0;
   printf("Parsing %s, connect %d\n", argv[2], connect);
 
-  int result = http_parser_parse_url(argv[2], len, connect, &u);
+  result = http_parser_parse_url(argv[2], len, connect, &u);
   if (result != 0) {
     printf("Parse error : %d\n", result);
     return result;
index 70cc9bd..0fa1c36 100644 (file)
@@ -56,19 +56,40 @@ 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 (settings->on_##FOR) {                                          \
-    if (0 != settings->on_##FOR(parser)) {                           \
+  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 (HTTP_PARSER_ERRNO(parser) != HPE_OK) {                       \
+    if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) {             \
       return (ER);                                                   \
     }                                                                \
   }                                                                  \
@@ -86,13 +107,16 @@ do {                                                                 \
   assert(HTTP_PARSER_ERRNO(parser) == HPE_OK);                       \
                                                                      \
   if (FOR##_mark) {                                                  \
-    if (settings->on_##FOR) {                                        \
-      if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) {      \
+    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 (HTTP_PARSER_ERRNO(parser) != HPE_OK) {                     \
+      if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) {           \
         return (ER);                                                 \
       }                                                              \
     }                                                                \
@@ -116,6 +140,26 @@ do {                                                                 \
   }                                                                  \
 } 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"
@@ -334,12 +378,16 @@ enum header_states
   , 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
@@ -371,6 +419,8 @@ enum http_host_state
   (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))
@@ -586,6 +636,7 @@ size_t http_parser_execute (http_parser *parser,
   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) {
@@ -593,7 +644,7 @@ size_t http_parser_execute (http_parser *parser,
   }
 
   if (len == 0) {
-    switch (parser->state) {
+    switch (CURRENT_STATE()) {
       case s_body_identity_eof:
         /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if
          * we got paused.
@@ -614,11 +665,11 @@ size_t http_parser_execute (http_parser *parser,
   }
 
 
-  if (parser->state == s_header_field)
+  if (CURRENT_STATE() == s_header_field)
     header_field_mark = data;
-  if (parser->state == s_header_value)
+  if (CURRENT_STATE() == s_header_value)
     header_value_mark = data;
-  switch (parser->state) {
+  switch (CURRENT_STATE()) {
   case s_req_path:
   case s_req_schema:
   case s_req_schema_slash:
@@ -635,38 +686,24 @@ size_t http_parser_execute (http_parser *parser,
   case s_res_status:
     status_mark = data;
     break;
+  default:
+    break;
   }
 
   for (p=data; p != data + len; p++) {
     ch = *p;
 
-    if (PARSING_HEADER(parser->state)) {
-      ++parser->nread;
-      /* 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.
-       */
-      if (parser->nread > HTTP_MAX_HEADER_SIZE) {
-        SET_ERRNO(HPE_HEADER_OVERFLOW);
-        goto error;
-      }
-    }
+    if (PARSING_HEADER(CURRENT_STATE()))
+      COUNT_HEADER_SIZE(1);
 
-    reexecute_byte:
-    switch (parser->state) {
+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 (ch == CR || ch == LF)
+        if (LIKELY(ch == CR || ch == LF))
           break;
 
         SET_ERRNO(HPE_CLOSED_CONNECTION);
@@ -680,13 +717,13 @@ size_t http_parser_execute (http_parser *parser,
         parser->content_length = ULLONG_MAX;
 
         if (ch == 'H') {
-          parser->state = s_res_or_resp_H;
+          UPDATE_STATE(s_res_or_resp_H);
 
           CALLBACK_NOTIFY(message_begin);
         } else {
           parser->type = HTTP_REQUEST;
-          parser->state = s_start_req;
-          goto reexecute_byte;
+          UPDATE_STATE(s_start_req);
+          REEXECUTE();
         }
 
         break;
@@ -695,9 +732,9 @@ size_t http_parser_execute (http_parser *parser,
       case s_res_or_resp_H:
         if (ch == 'T') {
           parser->type = HTTP_RESPONSE;
-          parser->state = s_res_HT;
+          UPDATE_STATE(s_res_HT);
         } else {
-          if (ch != 'E') {
+          if (UNLIKELY(ch != 'E')) {
             SET_ERRNO(HPE_INVALID_CONSTANT);
             goto error;
           }
@@ -705,7 +742,7 @@ size_t http_parser_execute (http_parser *parser,
           parser->type = HTTP_REQUEST;
           parser->method = HTTP_HEAD;
           parser->index = 2;
-          parser->state = s_req_method;
+          UPDATE_STATE(s_req_method);
         }
         break;
 
@@ -716,7 +753,7 @@ size_t http_parser_execute (http_parser *parser,
 
         switch (ch) {
           case 'H':
-            parser->state = s_res_H;
+            UPDATE_STATE(s_res_H);
             break;
 
           case CR:
@@ -734,39 +771,39 @@ size_t http_parser_execute (http_parser *parser,
 
       case s_res_H:
         STRICT_CHECK(ch != 'T');
-        parser->state = s_res_HT;
+        UPDATE_STATE(s_res_HT);
         break;
 
       case s_res_HT:
         STRICT_CHECK(ch != 'T');
-        parser->state = s_res_HTT;
+        UPDATE_STATE(s_res_HTT);
         break;
 
       case s_res_HTT:
         STRICT_CHECK(ch != 'P');
-        parser->state = s_res_HTTP;
+        UPDATE_STATE(s_res_HTTP);
         break;
 
       case s_res_HTTP:
         STRICT_CHECK(ch != '/');
-        parser->state = s_res_first_http_major;
+        UPDATE_STATE(s_res_first_http_major);
         break;
 
       case s_res_first_http_major:
-        if (ch < '0' || ch > '9') {
+        if (UNLIKELY(ch < '0' || ch > '9')) {
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
         }
 
         parser->http_major = ch - '0';
-        parser->state = s_res_http_major;
+        UPDATE_STATE(s_res_http_major);
         break;
 
       /* major HTTP version or dot */
       case s_res_http_major:
       {
         if (ch == '.') {
-          parser->state = s_res_first_http_minor;
+          UPDATE_STATE(s_res_first_http_minor);
           break;
         }
 
@@ -778,7 +815,7 @@ size_t http_parser_execute (http_parser *parser,
         parser->http_major *= 10;
         parser->http_major += ch - '0';
 
-        if (parser->http_major > 999) {
+        if (UNLIKELY(parser->http_major > 999)) {
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
         }
@@ -788,24 +825,24 @@ size_t http_parser_execute (http_parser *parser,
 
       /* first digit of minor HTTP version */
       case s_res_first_http_minor:
-        if (!IS_NUM(ch)) {
+        if (UNLIKELY(!IS_NUM(ch))) {
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
         }
 
         parser->http_minor = ch - '0';
-        parser->state = s_res_http_minor;
+        UPDATE_STATE(s_res_http_minor);
         break;
 
       /* minor HTTP version or end of request line */
       case s_res_http_minor:
       {
         if (ch == ' ') {
-          parser->state = s_res_first_status_code;
+          UPDATE_STATE(s_res_first_status_code);
           break;
         }
 
-        if (!IS_NUM(ch)) {
+        if (UNLIKELY(!IS_NUM(ch))) {
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
         }
@@ -813,7 +850,7 @@ size_t http_parser_execute (http_parser *parser,
         parser->http_minor *= 10;
         parser->http_minor += ch - '0';
 
-        if (parser->http_minor > 999) {
+        if (UNLIKELY(parser->http_minor > 999)) {
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
         }
@@ -832,7 +869,7 @@ size_t http_parser_execute (http_parser *parser,
           goto error;
         }
         parser->status_code = ch - '0';
-        parser->state = s_res_status_code;
+        UPDATE_STATE(s_res_status_code);
         break;
       }
 
@@ -841,13 +878,13 @@ size_t http_parser_execute (http_parser *parser,
         if (!IS_NUM(ch)) {
           switch (ch) {
             case ' ':
-              parser->state = s_res_status_start;
+              UPDATE_STATE(s_res_status_start);
               break;
             case CR:
-              parser->state = s_res_line_almost_done;
+              UPDATE_STATE(s_res_line_almost_done);
               break;
             case LF:
-              parser->state = s_header_field_start;
+              UPDATE_STATE(s_header_field_start);
               break;
             default:
               SET_ERRNO(HPE_INVALID_STATUS);
@@ -859,7 +896,7 @@ size_t http_parser_execute (http_parser *parser,
         parser->status_code *= 10;
         parser->status_code += ch - '0';
 
-        if (parser->status_code > 999) {
+        if (UNLIKELY(parser->status_code > 999)) {
           SET_ERRNO(HPE_INVALID_STATUS);
           goto error;
         }
@@ -870,30 +907,30 @@ size_t http_parser_execute (http_parser *parser,
       case s_res_status_start:
       {
         if (ch == CR) {
-          parser->state = s_res_line_almost_done;
+          UPDATE_STATE(s_res_line_almost_done);
           break;
         }
 
         if (ch == LF) {
-          parser->state = s_header_field_start;
+          UPDATE_STATE(s_header_field_start);
           break;
         }
 
         MARK(status);
-        parser->state = s_res_status;
+        UPDATE_STATE(s_res_status);
         parser->index = 0;
         break;
       }
 
       case s_res_status:
         if (ch == CR) {
-          parser->state = s_res_line_almost_done;
+          UPDATE_STATE(s_res_line_almost_done);
           CALLBACK_DATA(status);
           break;
         }
 
         if (ch == LF) {
-          parser->state = s_header_field_start;
+          UPDATE_STATE(s_header_field_start);
           CALLBACK_DATA(status);
           break;
         }
@@ -902,7 +939,7 @@ size_t http_parser_execute (http_parser *parser,
 
       case s_res_line_almost_done:
         STRICT_CHECK(ch != LF);
-        parser->state = s_header_field_start;
+        UPDATE_STATE(s_header_field_start);
         break;
 
       case s_start_req:
@@ -912,7 +949,7 @@ size_t http_parser_execute (http_parser *parser,
         parser->flags = 0;
         parser->content_length = ULLONG_MAX;
 
-        if (!IS_ALPHA(ch)) {
+        if (UNLIKELY(!IS_ALPHA(ch))) {
           SET_ERRNO(HPE_INVALID_METHOD);
           goto error;
         }
@@ -925,7 +962,7 @@ size_t http_parser_execute (http_parser *parser,
           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 */ 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;
@@ -939,7 +976,7 @@ size_t http_parser_execute (http_parser *parser,
             SET_ERRNO(HPE_INVALID_METHOD);
             goto error;
         }
-        parser->state = s_req_method;
+        UPDATE_STATE(s_req_method);
 
         CALLBACK_NOTIFY(message_begin);
 
@@ -949,14 +986,14 @@ size_t http_parser_execute (http_parser *parser,
       case s_req_method:
       {
         const char *matcher;
-        if (ch == '\0') {
+        if (UNLIKELY(ch == '\0')) {
           SET_ERRNO(HPE_INVALID_METHOD);
           goto error;
         }
 
         matcher = method_strings[parser->method];
         if (ch == ' ' && matcher[parser->index] == '\0') {
-          parser->state = s_req_spaces_before_url;
+          UPDATE_STATE(s_req_spaces_before_url);
         } else if (ch == matcher[parser->index]) {
           ; /* nada */
         } else if (parser->method == HTTP_CONNECT) {
@@ -977,6 +1014,8 @@ size_t http_parser_execute (http_parser *parser,
             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;
@@ -1035,11 +1074,11 @@ size_t http_parser_execute (http_parser *parser,
 
         MARK(url);
         if (parser->method == HTTP_CONNECT) {
-          parser->state = s_req_server_start;
+          UPDATE_STATE(s_req_server_start);
         }
 
-        parser->state = parse_url_char((enum state)parser->state, ch);
-        if (parser->state == s_dead) {
+        UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
+        if (UNLIKELY(CURRENT_STATE() == s_dead)) {
           SET_ERRNO(HPE_INVALID_URL);
           goto error;
         }
@@ -1060,8 +1099,8 @@ size_t http_parser_execute (http_parser *parser,
             SET_ERRNO(HPE_INVALID_URL);
             goto error;
           default:
-            parser->state = parse_url_char((enum state)parser->state, ch);
-            if (parser->state == s_dead) {
+            UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
+            if (UNLIKELY(CURRENT_STATE() == s_dead)) {
               SET_ERRNO(HPE_INVALID_URL);
               goto error;
             }
@@ -1080,21 +1119,21 @@ size_t http_parser_execute (http_parser *parser,
       {
         switch (ch) {
           case ' ':
-            parser->state = s_req_http_start;
+            UPDATE_STATE(s_req_http_start);
             CALLBACK_DATA(url);
             break;
           case CR:
           case LF:
             parser->http_major = 0;
             parser->http_minor = 9;
-            parser->state = (ch == CR) ?
+            UPDATE_STATE((ch == CR) ?
               s_req_line_almost_done :
-              s_header_field_start;
+              s_header_field_start);
             CALLBACK_DATA(url);
             break;
           default:
-            parser->state = parse_url_char((enum state)parser->state, ch);
-            if (parser->state == s_dead) {
+            UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
+            if (UNLIKELY(CURRENT_STATE() == s_dead)) {
               SET_ERRNO(HPE_INVALID_URL);
               goto error;
             }
@@ -1105,7 +1144,7 @@ size_t http_parser_execute (http_parser *parser,
       case s_req_http_start:
         switch (ch) {
           case 'H':
-            parser->state = s_req_http_H;
+            UPDATE_STATE(s_req_http_H);
             break;
           case ' ':
             break;
@@ -1117,44 +1156,44 @@ size_t http_parser_execute (http_parser *parser,
 
       case s_req_http_H:
         STRICT_CHECK(ch != 'T');
-        parser->state = s_req_http_HT;
+        UPDATE_STATE(s_req_http_HT);
         break;
 
       case s_req_http_HT:
         STRICT_CHECK(ch != 'T');
-        parser->state = s_req_http_HTT;
+        UPDATE_STATE(s_req_http_HTT);
         break;
 
       case s_req_http_HTT:
         STRICT_CHECK(ch != 'P');
-        parser->state = s_req_http_HTTP;
+        UPDATE_STATE(s_req_http_HTTP);
         break;
 
       case s_req_http_HTTP:
         STRICT_CHECK(ch != '/');
-        parser->state = s_req_first_http_major;
+        UPDATE_STATE(s_req_first_http_major);
         break;
 
       /* first digit of major HTTP version */
       case s_req_first_http_major:
-        if (ch < '1' || ch > '9') {
+        if (UNLIKELY(ch < '1' || ch > '9')) {
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
         }
 
         parser->http_major = ch - '0';
-        parser->state = s_req_http_major;
+        UPDATE_STATE(s_req_http_major);
         break;
 
       /* major HTTP version or dot */
       case s_req_http_major:
       {
         if (ch == '.') {
-          parser->state = s_req_first_http_minor;
+          UPDATE_STATE(s_req_first_http_minor);
           break;
         }
 
-        if (!IS_NUM(ch)) {
+        if (UNLIKELY(!IS_NUM(ch))) {
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
         }
@@ -1162,7 +1201,7 @@ size_t http_parser_execute (http_parser *parser,
         parser->http_major *= 10;
         parser->http_major += ch - '0';
 
-        if (parser->http_major > 999) {
+        if (UNLIKELY(parser->http_major > 999)) {
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
         }
@@ -1172,31 +1211,31 @@ size_t http_parser_execute (http_parser *parser,
 
       /* first digit of minor HTTP version */
       case s_req_first_http_minor:
-        if (!IS_NUM(ch)) {
+        if (UNLIKELY(!IS_NUM(ch))) {
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
         }
 
         parser->http_minor = ch - '0';
-        parser->state = s_req_http_minor;
+        UPDATE_STATE(s_req_http_minor);
         break;
 
       /* minor HTTP version or end of request line */
       case s_req_http_minor:
       {
         if (ch == CR) {
-          parser->state = s_req_line_almost_done;
+          UPDATE_STATE(s_req_line_almost_done);
           break;
         }
 
         if (ch == LF) {
-          parser->state = s_header_field_start;
+          UPDATE_STATE(s_header_field_start);
           break;
         }
 
         /* XXX allow spaces after digit? */
 
-        if (!IS_NUM(ch)) {
+        if (UNLIKELY(!IS_NUM(ch))) {
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
         }
@@ -1204,7 +1243,7 @@ size_t http_parser_execute (http_parser *parser,
         parser->http_minor *= 10;
         parser->http_minor += ch - '0';
 
-        if (parser->http_minor > 999) {
+        if (UNLIKELY(parser->http_minor > 999)) {
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
         }
@@ -1215,32 +1254,32 @@ size_t http_parser_execute (http_parser *parser,
       /* end of request line */
       case s_req_line_almost_done:
       {
-        if (ch != LF) {
+        if (UNLIKELY(ch != LF)) {
           SET_ERRNO(HPE_LF_EXPECTED);
           goto error;
         }
 
-        parser->state = s_header_field_start;
+        UPDATE_STATE(s_header_field_start);
         break;
       }
 
       case s_header_field_start:
       {
         if (ch == CR) {
-          parser->state = s_headers_almost_done;
+          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*/
-          parser->state = s_headers_almost_done;
-          goto reexecute_byte;
+          UPDATE_STATE(s_headers_almost_done);
+          REEXECUTE();
         }
 
         c = TOKEN(ch);
 
-        if (!c) {
+        if (UNLIKELY(!c)) {
           SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
           goto error;
         }
@@ -1248,7 +1287,7 @@ size_t http_parser_execute (http_parser *parser,
         MARK(header_field);
 
         parser->index = 0;
-        parser->state = s_header_field;
+        UPDATE_STATE(s_header_field);
 
         switch (c) {
           case 'c':
@@ -1276,9 +1315,14 @@ size_t http_parser_execute (http_parser *parser,
 
       case s_header_field:
       {
-        c = TOKEN(ch);
+        const char* start = p;
+        for (; p != data + len; p++) {
+          ch = *p;
+          c = TOKEN(ch);
+
+          if (!c)
+            break;
 
-        if (c) {
           switch (parser->header_state) {
             case h_general:
               break;
@@ -1379,23 +1423,17 @@ size_t http_parser_execute (http_parser *parser,
               assert(0 && "Unknown header_state");
               break;
           }
-          break;
         }
 
-        if (ch == ':') {
-          parser->state = s_header_value_discard_ws;
-          CALLBACK_DATA(header_field);
-          break;
-        }
+        COUNT_HEADER_SIZE(p - start);
 
-        if (ch == CR) {
-          parser->state = s_header_almost_done;
-          CALLBACK_DATA(header_field);
+        if (p == data + len) {
+          --p;
           break;
         }
 
-        if (ch == LF) {
-          parser->state = s_header_field_start;
+        if (ch == ':') {
+          UPDATE_STATE(s_header_value_discard_ws);
           CALLBACK_DATA(header_field);
           break;
         }
@@ -1408,12 +1446,12 @@ size_t http_parser_execute (http_parser *parser,
         if (ch == ' ' || ch == '\t') break;
 
         if (ch == CR) {
-          parser->state = s_header_value_discard_ws_almost_done;
+          UPDATE_STATE(s_header_value_discard_ws_almost_done);
           break;
         }
 
         if (ch == LF) {
-          parser->state = s_header_value_discard_lws;
+          UPDATE_STATE(s_header_value_discard_lws);
           break;
         }
 
@@ -1423,7 +1461,7 @@ size_t http_parser_execute (http_parser *parser,
       {
         MARK(header_value);
 
-        parser->state = s_header_value;
+        UPDATE_STATE(s_header_value);
         parser->index = 0;
 
         c = LOWER(ch);
@@ -1444,7 +1482,7 @@ size_t http_parser_execute (http_parser *parser,
             break;
 
           case h_content_length:
-            if (!IS_NUM(ch)) {
+            if (UNLIKELY(!IS_NUM(ch))) {
               SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
               goto error;
             }
@@ -1459,11 +1497,17 @@ size_t http_parser_execute (http_parser *parser,
             /* 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_general;
+              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;
@@ -1473,98 +1517,187 @@ size_t http_parser_execute (http_parser *parser,
 
       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 == CR) {
-          parser->state = s_header_almost_done;
-          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();
+          }
 
-        if (ch == LF) {
-          parser->state = s_header_almost_done;
-          CALLBACK_DATA_NOADVANCE(header_value);
-          goto reexecute_byte;
-        }
+          c = LOWER(ch);
 
-        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;
 
-        switch (parser->header_state) {
-          case h_general:
-            break;
+              break;
+            }
 
-          case h_connection:
-          case h_transfer_encoding:
-            assert(0 && "Shouldn't get here.");
-            break;
+            case h_connection:
+            case h_transfer_encoding:
+              assert(0 && "Shouldn't get here.");
+              break;
 
-          case h_content_length:
-          {
-            uint64_t t;
+            case h_content_length:
+            {
+              uint64_t t;
 
-            if (ch == ' ') break;
+              if (ch == ' ') break;
 
-            if (!IS_NUM(ch)) {
-              SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
-              goto error;
-            }
+              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';
+              t = parser->content_length;
+              t *= 10;
+              t += ch - '0';
 
-            /* Overflow? Test against a conservative limit for simplicity. */
-            if ((ULLONG_MAX - 10) / 10 < parser->content_length) {
-              SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
-              goto error;
+              /* 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;
             }
 
-            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;
 
-          /* Transfer-Encoding: chunked */
-          case h_matching_transfer_encoding_chunked:
-            parser->index++;
-            if (parser->index > sizeof(CHUNKED)-1
-                || c != CHUNKED[parser->index]) {
-              parser->header_state = h_general;
-            } else if (parser->index == sizeof(CHUNKED)-2) {
-              parser->header_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]) {
-              parser->header_state = h_general;
-            } else if (parser->index == sizeof(KEEP_ALIVE)-2) {
-              parser->header_state = h_connection_keep_alive;
-            }
-            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]) {
-              parser->header_state = h_general;
-            } else if (parser->index == sizeof(CLOSE)-2) {
-              parser->header_state = h_connection_close;
-            }
-            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;
 
-          case h_transfer_encoding_chunked:
-          case h_connection_keep_alive:
-          case h_connection_close:
-            if (ch != ' ') parser->header_state = h_general;
-            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;
 
-          default:
-            parser->state = s_header_value;
-            parser->header_state = h_general;
-            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;
       }
 
@@ -1572,15 +1705,15 @@ size_t http_parser_execute (http_parser *parser,
       {
         STRICT_CHECK(ch != LF);
 
-        parser->state = s_header_value_lws;
+        UPDATE_STATE(s_header_value_lws);
         break;
       }
 
       case s_header_value_lws:
       {
         if (ch == ' ' || ch == '\t') {
-          parser->state = s_header_value_start;
-          goto reexecute_byte;
+          UPDATE_STATE(s_header_value_start);
+          REEXECUTE();
         }
 
         /* finished the header */
@@ -1594,32 +1727,52 @@ size_t http_parser_execute (http_parser *parser,
           case h_transfer_encoding_chunked:
             parser->flags |= F_CHUNKED;
             break;
+          case h_connection_upgrade:
+            parser->flags |= F_CONNECTION_UPGRADE;
+            break;
           default:
             break;
         }
 
-        parser->state = s_header_field_start;
-        goto reexecute_byte;
+        UPDATE_STATE(s_header_field_start);
+        REEXECUTE();
       }
 
       case s_header_value_discard_ws_almost_done:
       {
         STRICT_CHECK(ch != LF);
-        parser->state = s_header_value_discard_lws;
+        UPDATE_STATE(s_header_value_discard_lws);
         break;
       }
 
       case s_header_value_discard_lws:
       {
         if (ch == ' ' || ch == '\t') {
-          parser->state = s_header_value_discard_ws;
+          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);
-          parser->state = s_header_field_start;
+          UPDATE_STATE(s_header_field_start);
           CALLBACK_DATA_NOADVANCE(header_value);
-          goto reexecute_byte;
+          REEXECUTE();
         }
       }
 
@@ -1629,16 +1782,18 @@ size_t http_parser_execute (http_parser *parser,
 
         if (parser->flags & F_TRAILING) {
           /* End of a chunked request */
-          parser->state = NEW_MESSAGE();
-          CALLBACK_NOTIFY(message_complete);
-          break;
+          UPDATE_STATE(s_message_done);
+          CALLBACK_NOTIFY_NOADVANCE(chunk_complete);
+          REEXECUTE();
         }
 
-        parser->state = s_headers_done;
+        UPDATE_STATE(s_headers_done);
 
         /* Set this here so that on_headers_complete() callbacks can see it */
         parser->upgrade =
-          (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT);
+          ((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
@@ -1660,15 +1815,15 @@ size_t http_parser_execute (http_parser *parser,
 
             default:
               SET_ERRNO(HPE_CB_headers_complete);
-              return p - data; /* Error */
+              RETURN(p - data); /* Error */
           }
         }
 
         if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
-          return p - data;
+          RETURN(p - data);
         }
 
-        goto reexecute_byte;
+        REEXECUTE();
       }
 
       case s_headers_done:
@@ -1677,36 +1832,39 @@ size_t http_parser_execute (http_parser *parser,
 
         parser->nread = 0;
 
-        /* Exit, the rest of the connect is in a different protocol. */
-        if (parser->upgrade) {
-          parser->state = NEW_MESSAGE();
+        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;
+          RETURN((p - data) + 1);
         }
 
         if (parser->flags & F_SKIPBODY) {
-          parser->state = NEW_MESSAGE();
+          UPDATE_STATE(NEW_MESSAGE());
           CALLBACK_NOTIFY(message_complete);
         } else if (parser->flags & F_CHUNKED) {
           /* chunked encoding - ignore Content-Length header */
-          parser->state = s_chunk_size_start;
+          UPDATE_STATE(s_chunk_size_start);
         } else {
           if (parser->content_length == 0) {
             /* Content-Length header given but zero: Content-Length: 0\r\n */
-            parser->state = NEW_MESSAGE();
+            UPDATE_STATE(NEW_MESSAGE());
             CALLBACK_NOTIFY(message_complete);
           } else if (parser->content_length != ULLONG_MAX) {
             /* Content-Length header given and non-zero */
-            parser->state = s_body_identity;
+            UPDATE_STATE(s_body_identity);
           } else {
             if (parser->type == HTTP_REQUEST ||
                 !http_message_needs_eof(parser)) {
               /* Assume content-length 0 - read the next */
-              parser->state = NEW_MESSAGE();
+              UPDATE_STATE(NEW_MESSAGE());
               CALLBACK_NOTIFY(message_complete);
             } else {
               /* Read body until EOF */
-              parser->state = s_body_identity_eof;
+              UPDATE_STATE(s_body_identity_eof);
             }
           }
         }
@@ -1732,7 +1890,7 @@ size_t http_parser_execute (http_parser *parser,
         p += to_read - 1;
 
         if (parser->content_length == 0) {
-          parser->state = s_message_done;
+          UPDATE_STATE(s_message_done);
 
           /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte.
            *
@@ -1744,7 +1902,7 @@ size_t http_parser_execute (http_parser *parser,
            * important for applications, but let's keep it for now.
            */
           CALLBACK_DATA_(body, p - body_mark + 1, p - data);
-          goto reexecute_byte;
+          REEXECUTE();
         }
 
         break;
@@ -1758,8 +1916,12 @@ size_t http_parser_execute (http_parser *parser,
         break;
 
       case s_message_done:
-        parser->state = NEW_MESSAGE();
+        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:
@@ -1768,13 +1930,13 @@ size_t http_parser_execute (http_parser *parser,
         assert(parser->flags & F_CHUNKED);
 
         unhex_val = unhex[(unsigned char)ch];
-        if (unhex_val == -1) {
+        if (UNLIKELY(unhex_val == -1)) {
           SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
           goto error;
         }
 
         parser->content_length = unhex_val;
-        parser->state = s_chunk_size;
+        UPDATE_STATE(s_chunk_size);
         break;
       }
 
@@ -1785,7 +1947,7 @@ size_t http_parser_execute (http_parser *parser,
         assert(parser->flags & F_CHUNKED);
 
         if (ch == CR) {
-          parser->state = s_chunk_size_almost_done;
+          UPDATE_STATE(s_chunk_size_almost_done);
           break;
         }
 
@@ -1793,7 +1955,7 @@ size_t http_parser_execute (http_parser *parser,
 
         if (unhex_val == -1) {
           if (ch == ';' || ch == ' ') {
-            parser->state = s_chunk_parameters;
+            UPDATE_STATE(s_chunk_parameters);
             break;
           }
 
@@ -1806,7 +1968,7 @@ size_t http_parser_execute (http_parser *parser,
         t += unhex_val;
 
         /* Overflow? Test against a conservative limit for simplicity. */
-        if ((ULLONG_MAX - 16) / 16 < parser->content_length) {
+        if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) {
           SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
           goto error;
         }
@@ -1820,7 +1982,7 @@ size_t http_parser_execute (http_parser *parser,
         assert(parser->flags & F_CHUNKED);
         /* just ignore this shit. TODO check for overflow */
         if (ch == CR) {
-          parser->state = s_chunk_size_almost_done;
+          UPDATE_STATE(s_chunk_size_almost_done);
           break;
         }
         break;
@@ -1835,10 +1997,11 @@ size_t http_parser_execute (http_parser *parser,
 
         if (parser->content_length == 0) {
           parser->flags |= F_TRAILING;
-          parser->state = s_header_field_start;
+          UPDATE_STATE(s_header_field_start);
         } else {
-          parser->state = s_chunk_data;
+          UPDATE_STATE(s_chunk_data);
         }
+        CALLBACK_NOTIFY(chunk_header);
         break;
       }
 
@@ -1859,7 +2022,7 @@ size_t http_parser_execute (http_parser *parser,
         p += to_read - 1;
 
         if (parser->content_length == 0) {
-          parser->state = s_chunk_data_almost_done;
+          UPDATE_STATE(s_chunk_data_almost_done);
         }
 
         break;
@@ -1869,7 +2032,7 @@ size_t http_parser_execute (http_parser *parser,
         assert(parser->flags & F_CHUNKED);
         assert(parser->content_length == 0);
         STRICT_CHECK(ch != CR);
-        parser->state = s_chunk_data_done;
+        UPDATE_STATE(s_chunk_data_done);
         CALLBACK_DATA(body);
         break;
 
@@ -1877,7 +2040,8 @@ size_t http_parser_execute (http_parser *parser,
         assert(parser->flags & F_CHUNKED);
         STRICT_CHECK(ch != LF);
         parser->nread = 0;
-        parser->state = s_chunk_size_start;
+        UPDATE_STATE(s_chunk_size_start);
+        CALLBACK_NOTIFY(chunk_complete);
         break;
 
       default:
@@ -1909,14 +2073,14 @@ size_t http_parser_execute (http_parser *parser,
   CALLBACK_DATA_NOADVANCE(body);
   CALLBACK_DATA_NOADVANCE(status);
 
-  return len;
+  RETURN(len);
 
 error:
   if (HTTP_PARSER_ERRNO(parser) == HPE_OK) {
     SET_ERRNO(HPE_UNKNOWN);
   }
 
-  return (p - data);
+  RETURN(p - data);
 }
 
 
@@ -1981,15 +2145,23 @@ http_parser_init (http_parser *parser, enum http_parser_type t)
   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(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));
+  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(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));
+  assert(((size_t) err) <
+      (sizeof(http_strerror_tab) / sizeof(http_strerror_tab[0])));
   return http_strerror_tab[err].description;
 }
 
@@ -2142,7 +2314,7 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
 
   u->port = u->field_set = 0;
   s = is_connect ? s_req_server_start : s_req_spaces_before_url;
-  uf = old_uf = UF_MAX;
+  old_uf = UF_MAX;
 
   for (p = buf; p < buf + buflen; p++) {
     s = parse_url_char(s, *p);
index ec61a12..eb71bf9 100644 (file)
@@ -26,7 +26,7 @@ extern "C" {
 
 /* Also update SONAME in the Makefile whenever you change these. */
 #define HTTP_PARSER_VERSION_MAJOR 2
-#define HTTP_PARSER_VERSION_MINOR 3
+#define HTTP_PARSER_VERSION_MINOR 5
 #define HTTP_PARSER_VERSION_PATCH 0
 
 #include <sys/types.h>
@@ -52,9 +52,16 @@ typedef unsigned __int64 uint64_t;
 # define HTTP_PARSER_STRICT 1
 #endif
 
-/* Maximium header size allowed */
-#define HTTP_MAX_HEADER_SIZE (80*1024)
-
+/* Maximium header size allowed. If the macro is not defined
+ * before including this header then the default is used. To
+ * change the maximum header size, define the macro in the build
+ * environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove
+ * the effective limit on the size of the header, define the macro
+ * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)
+ */
+#ifndef HTTP_MAX_HEADER_SIZE
+# define HTTP_MAX_HEADER_SIZE (80*1024)
+#endif
 
 typedef struct http_parser http_parser;
 typedef struct http_parser_settings http_parser_settings;
@@ -69,7 +76,7 @@ typedef struct http_parser_settings http_parser_settings;
  * 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 call arbitrarally
+ * 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.
  */
@@ -110,6 +117,8 @@ typedef int (*http_cb) (http_parser*);
   /* RFC-5789 */                    \
   XX(24, PATCH,       PATCH)        \
   XX(25, PURGE,       PURGE)        \
+  /* CalDAV */                      \
+  XX(26, MKCALENDAR,  MKCALENDAR)   \
 
 enum http_method
   {
@@ -127,9 +136,10 @@ enum flags
   { F_CHUNKED               = 1 << 0
   , F_CONNECTION_KEEP_ALIVE = 1 << 1
   , F_CONNECTION_CLOSE      = 1 << 2
-  , F_TRAILING              = 1 << 3
-  , F_UPGRADE               = 1 << 4
-  , F_SKIPBODY              = 1 << 5
+  , F_CONNECTION_UPGRADE    = 1 << 3
+  , F_TRAILING              = 1 << 4
+  , F_UPGRADE               = 1 << 5
+  , F_SKIPBODY              = 1 << 6
   };
 
 
@@ -150,6 +160,8 @@ enum flags
   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")        \
@@ -194,8 +206,8 @@ enum http_errno {
 struct http_parser {
   /** PRIVATE **/
   unsigned int type : 2;         /* enum http_parser_type */
-  unsigned int flags : 6;        /* F_* values from 'flags' enum; semi-public */
-  unsigned int state : 8;        /* enum state from http_parser.c */
+  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 */
 
@@ -230,6 +242,11 @@ struct http_parser_settings {
   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;
 };
 
 
@@ -271,13 +288,20 @@ struct http_parser_url {
  *   unsigned major = (version >> 16) & 255;
  *   unsigned minor = (version >> 8) & 255;
  *   unsigned patch = version & 255;
- *   printf("http_parser v%u.%u.%u\n", major, minor, version);
+ *   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,
index 9799dc6..4c00571 100644 (file)
@@ -39,6 +39,7 @@
 
 #define MAX_HEADERS 13
 #define MAX_ELEMENT_SIZE 2048
+#define MAX_CHUNKS 16
 
 #define MIN(a,b) ((a) < (b) ? (a) : (b))
 
@@ -65,6 +66,10 @@ struct message {
   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;
@@ -301,6 +306,8 @@ const struct message requests[] =
     { { "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
@@ -327,6 +334,8 @@ const struct message requests[] =
     { { "Transfer-Encoding", "chunked" }
     }
   ,.body= "hello world"
+  ,.num_chunks_complete= 3
+  ,.chunk_lengths= { 5, 6 }
   }
 
 #define CHUNKED_W_TRAILING_HEADERS 10
@@ -357,6 +366,8 @@ const struct message requests[] =
     , { "Content-Type", "text/plain" }
     }
   ,.body= "hello world"
+  ,.num_chunks_complete= 3
+  ,.chunk_lengths= { 5, 6 }
   }
 
 #define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11
@@ -383,6 +394,8 @@ const struct message requests[] =
     { { "Transfer-Encoding", "chunked" }
     }
   ,.body= "hello world"
+  ,.num_chunks_complete= 3
+  ,.chunk_lengths= { 5, 6 }
   }
 
 #define WITH_QUOTES 12
@@ -950,6 +963,143 @@ const struct message requests[] =
   ,.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 */
 };
@@ -1110,7 +1260,8 @@ const struct message responses[] =
   ,.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
@@ -1264,6 +1415,8 @@ const struct message responses[] =
     , { "Connection", "close" }
     }
   ,.body= ""
+  ,.num_chunks_complete= 1
+  ,.chunk_lengths= {}
   }
 
 #define NON_ASCII_IN_STATUS_LINE 10
@@ -1446,6 +1599,7 @@ const struct message responses[] =
     }
   ,.body_size= 0
   ,.body= ""
+  ,.num_chunks_complete= 1
   }
 
 #if !HTTP_PARSER_STRICT
@@ -1519,6 +1673,8 @@ const struct message responses[] =
              , { "Transfer-Encoding", "chunked" }
              }
   ,.body= "\n"
+  ,.num_chunks_complete= 2
+  ,.chunk_lengths= { 1 }
   }
 
 #define EMPTY_REASON_PHRASE_AFTER_SPACE 20
@@ -1754,6 +1910,35 @@ response_status_cb (http_parser *p, const char *buf, size_t 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
@@ -1822,6 +2007,23 @@ dontcall_response_status_cb (http_parser *p, const char *buf, size_t len)
   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
@@ -1831,6 +2033,8 @@ static http_parser_settings settings_dontcall =
   ,.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
@@ -1901,6 +2105,22 @@ pause_response_status_cb (http_parser *p, const char *buf, size_t len)
   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
@@ -1910,6 +2130,8 @@ static http_parser_settings settings_pause =
   ,.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 =
@@ -1921,6 +2143,8 @@ static http_parser_settings settings =
   ,.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 =
@@ -1932,6 +2156,8 @@ static http_parser_settings settings_count_body =
   ,.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 =
@@ -1943,6 +2169,8 @@ static http_parser_settings settings_null =
   ,.on_body = 0
   ,.on_headers_complete = 0
   ,.on_message_complete = 0
+  ,.on_chunk_header = 0
+  ,.on_chunk_complete = 0
   };
 
 void
@@ -2111,6 +2339,12 @@ message_eq (int index, const struct message *expected)
     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;
@@ -2207,7 +2441,6 @@ print_error (const char *raw, size_t error_location)
         break;
 
       case '\n':
-        char_len = 2;
         fprintf(stderr, "\\n\n");
 
         if (this_line) goto print;
@@ -2825,7 +3058,7 @@ test_message (const struct message *message)
     if (msg1len) {
       read = parse(msg1, msg1len);
 
-      if (message->upgrade && parser->upgrade) {
+      if (message->upgrade && parser->upgrade && num_messages > 0) {
         messages[num_messages - 1].upgrade = msg1 + read;
         goto test;
       }
@@ -2910,15 +3143,11 @@ test_simple (const char *buf, enum http_errno err_expected)
 {
   parser_init(HTTP_REQUEST);
 
-  size_t parsed;
-  int pass;
   enum http_errno err;
 
-  parsed = parse(buf, strlen(buf));
-  pass = (parsed == strlen(buf));
+  parse(buf, strlen(buf));
   err = HTTP_PARSER_ERRNO(parser);
-  parsed = parse(NULL, 0);
-  pass &= (parsed == 0);
+  parse(NULL, 0);
 
   parser_free();
 
@@ -2964,6 +3193,22 @@ test_header_overflow_error (int req)
   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)
 {
@@ -3330,6 +3575,9 @@ main (void)
   test_parse_url();
   test_method_str();
 
+  //// NREAD
+  test_header_nread_value();
+
   //// OVERFLOW CONDITIONS
 
   test_header_overflow_error(HTTP_REQUEST);
@@ -3389,7 +3637,11 @@ main (void)
         , { "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);
   }
@@ -3476,6 +3728,13 @@ main (void)
     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"
@@ -3513,6 +3772,22 @@ main (void)
     "\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.
diff --git a/test/parallel/test-http-upgrade-yes-please.js b/test/parallel/test-http-upgrade-yes-please.js
deleted file mode 100644 (file)
index d9e5dc6..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-'use strict';
-
-var assert = require('assert');
-var http = require('http');
-var net = require('net');
-
-var upgrades = 0;
-process.on('exit', function() { assert.equal(upgrades, 1); });
-
-http.createServer(assert.fail).listen(0, '127.0.0.1', function() {
-  this.on('upgrade', function(req, conn, head) {
-    conn.destroy();
-    this.close();
-    upgrades += 1;
-  });
-  var options = { host: this.address().address, port: this.address().port };
-  net.connect(options, function() {
-    this.write('GET / HTTP/1.1\r\n' +
-               'Upgrade: Yes, please.\r\n' +
-               '\r\n');
-  });
-});