Browse Source

Import upstream version 2.8.1

Ben Noordhuis 6 years ago
parent
commit
c07ef69d42
8 changed files with 492 additions and 200 deletions
  1. 1 5
      LICENSE-MIT
  2. 25 14
      Makefile
  3. 5 5
      README.md
  4. 26 9
      bench.c
  5. 1 4
      contrib/parsertrace.c
  6. 93 123
      http_parser.c
  7. 74 3
      http_parser.h
  8. 267 37
      test.c

+ 1 - 5
LICENSE-MIT

@@ -1,8 +1,4 @@
-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.
+Copyright Joyent, Inc. and other Node contributors.
 
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to
 of this software and associated documentation files (the "Software"), to

+ 25 - 14
Makefile

@@ -21,16 +21,22 @@
 PLATFORM ?= $(shell sh -c 'uname -s | tr "[A-Z]" "[a-z]"')
 PLATFORM ?= $(shell sh -c 'uname -s | tr "[A-Z]" "[a-z]"')
 HELPER ?=
 HELPER ?=
 BINEXT ?=
 BINEXT ?=
+SOLIBNAME = libhttp_parser
+SOMAJOR = 2
+SOMINOR = 8
+SOREV   = 1
 ifeq (darwin,$(PLATFORM))
 ifeq (darwin,$(PLATFORM))
-SONAME ?= libhttp_parser.2.7.1.dylib
 SOEXT ?= dylib
 SOEXT ?= dylib
+SONAME ?= $(SOLIBNAME).$(SOMAJOR).$(SOMINOR).$(SOEXT)
+LIBNAME ?= $(SOLIBNAME).$(SOMAJOR).$(SOMINOR).$(SOREV).$(SOEXT)
 else ifeq (wine,$(PLATFORM))
 else ifeq (wine,$(PLATFORM))
 CC = winegcc
 CC = winegcc
 BINEXT = .exe.so
 BINEXT = .exe.so
 HELPER = wine
 HELPER = wine
 else
 else
-SONAME ?= libhttp_parser.so.2.7.1
 SOEXT ?= so
 SOEXT ?= so
+SONAME ?= $(SOLIBNAME).$(SOEXT).$(SOMAJOR).$(SOMINOR)
+LIBNAME ?= $(SOLIBNAME).$(SOEXT).$(SOMAJOR).$(SOMINOR).$(SOREV)
 endif
 endif
 
 
 CC?=gcc
 CC?=gcc
@@ -55,11 +61,13 @@ CFLAGS_LIB = $(CFLAGS_FAST) -fPIC
 LDFLAGS_LIB = $(LDFLAGS) -shared
 LDFLAGS_LIB = $(LDFLAGS) -shared
 
 
 INSTALL ?= install
 INSTALL ?= install
-PREFIX ?= $(DESTDIR)/usr/local
+PREFIX ?= /usr/local
 LIBDIR = $(PREFIX)/lib
 LIBDIR = $(PREFIX)/lib
 INCLUDEDIR = $(PREFIX)/include
 INCLUDEDIR = $(PREFIX)/include
 
 
-ifneq (darwin,$(PLATFORM))
+ifeq (darwin,$(PLATFORM))
+LDFLAGS_LIB += -Wl,-install_name,$(LIBDIR)/$(SONAME)
+else
 # TODO(bnoordhuis) The native SunOS linker expects -h rather than -soname...
 # TODO(bnoordhuis) The native SunOS linker expects -h rather than -soname...
 LDFLAGS_LIB += -Wl,-soname=$(SONAME)
 LDFLAGS_LIB += -Wl,-soname=$(SONAME)
 endif
 endif
@@ -102,7 +110,7 @@ libhttp_parser.o: http_parser.c http_parser.h Makefile
 	$(CC) $(CPPFLAGS_FAST) $(CFLAGS_LIB) -c http_parser.c -o libhttp_parser.o
 	$(CC) $(CPPFLAGS_FAST) $(CFLAGS_LIB) -c http_parser.c -o libhttp_parser.o
 
 
 library: libhttp_parser.o
 library: libhttp_parser.o
-	$(CC) $(LDFLAGS_LIB) -o $(SONAME) $<
+	$(CC) $(LDFLAGS_LIB) -o $(LIBNAME) $<
 
 
 package: http_parser.o
 package: http_parser.o
 	$(AR) rcs libhttp_parser.a http_parser.o
 	$(AR) rcs libhttp_parser.a http_parser.o
@@ -123,19 +131,22 @@ tags: http_parser.c http_parser.h test.c
 	ctags $^
 	ctags $^
 
 
 install: library
 install: library
-	$(INSTALL) -D  http_parser.h $(INCLUDEDIR)/http_parser.h
-	$(INSTALL) -D $(SONAME) $(LIBDIR)/$(SONAME)
-	ln -s $(LIBDIR)/$(SONAME) $(LIBDIR)/libhttp_parser.$(SOEXT)
+	$(INSTALL) -D  http_parser.h $(DESTDIR)$(INCLUDEDIR)/http_parser.h
+	$(INSTALL) -D $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(LIBNAME)
+	ln -s $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SONAME)
+	ln -s $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SOLIBNAME).$(SOEXT)
 
 
 install-strip: library
 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.$(SOEXT)
+	$(INSTALL) -D  http_parser.h $(DESTDIR)$(INCLUDEDIR)/http_parser.h
+	$(INSTALL) -D -s $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(LIBNAME)
+	ln -s $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SONAME)
+	ln -s $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SOLIBNAME).$(SOEXT)
 
 
 uninstall:
 uninstall:
-	rm $(INCLUDEDIR)/http_parser.h
-	rm $(LIBDIR)/$(SONAME)
-	rm $(LIBDIR)/libhttp_parser.so
+	rm $(DESTDIR)$(INCLUDEDIR)/http_parser.h
+	rm $(DESTDIR)$(LIBDIR)/$(SOLIBNAME).$(SOEXT)
+	rm $(DESTDIR)$(LIBDIR)/$(SONAME)
+	rm $(DESTDIR)$(LIBDIR)/$(LIBNAME)
 
 
 clean:
 clean:
 	rm -f *.o *.a tags test test_fast test_g \
 	rm -f *.o *.a tags test test_fast test_g \

+ 5 - 5
README.md

@@ -72,9 +72,9 @@ if (parser->upgrade) {
 }
 }
 ```
 ```
 
 
-HTTP needs to know where the end of the stream is. For example, sometimes
+`http_parser` needs to know where the end of the stream is. For example, sometimes
 servers send responses without Content-Length and expect the client to
 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
+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
 `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
 can still be encountered during an EOF, so one must still be prepared
 to receive them.
 to receive them.
@@ -93,7 +93,7 @@ the on_body callback.
 The Special Problem of Upgrade
 The Special Problem of Upgrade
 ------------------------------
 ------------------------------
 
 
-HTTP supports upgrading the connection to a different protocol. An
+`http_parser` supports upgrading the connection to a different protocol. An
 increasingly common example of this is the WebSocket protocol which sends
 increasingly common example of this is the WebSocket protocol which sends
 a request like
 a request like
 
 
@@ -144,7 +144,7 @@ parse a request, and then give a response over that socket. By instantiation
 of a thread-local struct containing relevant data (e.g. accepted socket,
 of a thread-local struct containing relevant data (e.g. accepted socket,
 allocated memory for callbacks to write into, etc), a parser's callbacks are
 allocated memory for callbacks to write into, etc), a parser's callbacks are
 able to communicate data between the scope of the thread and the scope of the
 able to communicate data between the scope of the thread and the scope of the
-callback in a threadsafe manner. This allows http-parser to be used in
+callback in a threadsafe manner. This allows `http_parser` to be used in
 multi-threaded contexts.
 multi-threaded contexts.
 
 
 Example:
 Example:
@@ -202,7 +202,7 @@ void http_parser_thread(socket_t sock) {
 
 
 In case you parse HTTP message in chunks (i.e. `read()` request line
 In case you parse HTTP message in chunks (i.e. `read()` request line
 from socket, parse, read half headers, parse, etc) your data callbacks
 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
+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
 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.
 buffer to avoid copying memory around if this fits your application.
 
 

+ 26 - 9
bench.c

@@ -20,10 +20,14 @@
  */
  */
 #include "http_parser.h"
 #include "http_parser.h"
 #include <assert.h>
 #include <assert.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdio.h>
 #include <string.h>
 #include <string.h>
 #include <sys/time.h>
 #include <sys/time.h>
 
 
+/* 8 gb */
+static const int64_t kBytes = 8LL << 30;
+
 static const char data[] =
 static const char data[] =
     "POST /joyent/http-parser HTTP/1.1\r\n"
     "POST /joyent/http-parser HTTP/1.1\r\n"
     "Host: github.com\r\n"
     "Host: github.com\r\n"
@@ -38,7 +42,7 @@ static const char data[] =
     "Referer: https://github.com/joyent/http-parser\r\n"
     "Referer: https://github.com/joyent/http-parser\r\n"
     "Connection: keep-alive\r\n"
     "Connection: keep-alive\r\n"
     "Transfer-Encoding: chunked\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";
+    "Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n";
 static const size_t data_len = sizeof(data) - 1;
 static const size_t data_len = sizeof(data) - 1;
 
 
 static int on_info(http_parser* p) {
 static int on_info(http_parser* p) {
@@ -67,13 +71,13 @@ int bench(int iter_count, int silent) {
   int err;
   int err;
   struct timeval start;
   struct timeval start;
   struct timeval end;
   struct timeval end;
-  float rps;
 
 
   if (!silent) {
   if (!silent) {
     err = gettimeofday(&start, NULL);
     err = gettimeofday(&start, NULL);
     assert(err == 0);
     assert(err == 0);
   }
   }
 
 
+  fprintf(stderr, "req_len=%d\n", (int) data_len);
   for (i = 0; i < iter_count; i++) {
   for (i = 0; i < iter_count; i++) {
     size_t parsed;
     size_t parsed;
     http_parser_init(&parser, HTTP_REQUEST);
     http_parser_init(&parser, HTTP_REQUEST);
@@ -83,17 +87,27 @@ int bench(int iter_count, int silent) {
   }
   }
 
 
   if (!silent) {
   if (!silent) {
+    double elapsed;
+    double bw;
+    double total;
+
     err = gettimeofday(&end, NULL);
     err = gettimeofday(&end, NULL);
     assert(err == 0);
     assert(err == 0);
 
 
     fprintf(stdout, "Benchmark result:\n");
     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);
+    elapsed = (double) (end.tv_sec - start.tv_sec) +
+              (end.tv_usec - start.tv_usec) * 1e-6f;
+
+    total = (double) iter_count * data_len;
+    bw = (double) total / elapsed;
+
+    fprintf(stdout, "%.2f mb | %.2f mb/s | %.2f req/sec | %.2f s\n",
+        (double) total / (1024 * 1024),
+        bw / (1024 * 1024),
+        (double) iter_count / elapsed,
+        elapsed);
 
 
-    rps = (float) iter_count / rps;
-    fprintf(stdout, "%f req/sec\n", rps);
     fflush(stdout);
     fflush(stdout);
   }
   }
 
 
@@ -101,11 +115,14 @@ int bench(int iter_count, int silent) {
 }
 }
 
 
 int main(int argc, char** argv) {
 int main(int argc, char** argv) {
+  int64_t iterations;
+
+  iterations = kBytes / (int64_t) data_len;
   if (argc == 2 && strcmp(argv[1], "infinite") == 0) {
   if (argc == 2 && strcmp(argv[1], "infinite") == 0) {
     for (;;)
     for (;;)
-      bench(5000000, 1);
+      bench(iterations, 1);
     return 0;
     return 0;
   } else {
   } else {
-    return bench(5000000, 0);
+    return bench(iterations, 0);
   }
   }
 }
 }

+ 1 - 4
contrib/parsertrace.c

@@ -1,7 +1,4 @@
-/* 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.
+/* Copyright Joyent, Inc. and other Node contributors.
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to
  * of this software and associated documentation files (the "Software"), to

+ 93 - 123
http_parser.c

@@ -1,7 +1,4 @@
-/* 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.
+/* Copyright Joyent, Inc. and other Node contributors.
  *
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to
  * of this software and associated documentation files (the "Software"), to
@@ -25,7 +22,6 @@
 #include <assert.h>
 #include <assert.h>
 #include <stddef.h>
 #include <stddef.h>
 #include <ctype.h>
 #include <ctype.h>
-#include <stdlib.h>
 #include <string.h>
 #include <string.h>
 #include <limits.h>
 #include <limits.h>
 
 
@@ -286,10 +282,10 @@ enum state
   , s_res_HT
   , s_res_HT
   , s_res_HTT
   , s_res_HTT
   , s_res_HTTP
   , s_res_HTTP
-  , s_res_first_http_major
   , s_res_http_major
   , s_res_http_major
-  , s_res_first_http_minor
+  , s_res_http_dot
   , s_res_http_minor
   , s_res_http_minor
+  , s_res_http_end
   , s_res_first_status_code
   , s_res_first_status_code
   , s_res_status_code
   , s_res_status_code
   , s_res_status_start
   , s_res_status_start
@@ -316,10 +312,10 @@ enum state
   , s_req_http_HT
   , s_req_http_HT
   , s_req_http_HTT
   , s_req_http_HTT
   , s_req_http_HTTP
   , s_req_http_HTTP
-  , s_req_first_http_major
   , s_req_http_major
   , s_req_http_major
-  , s_req_first_http_minor
+  , s_req_http_dot
   , s_req_http_minor
   , s_req_http_minor
+  , s_req_http_end
   , s_req_line_almost_done
   , s_req_line_almost_done
 
 
   , s_header_field_start
   , s_header_field_start
@@ -374,6 +370,8 @@ enum header_states
 
 
   , h_connection
   , h_connection
   , h_content_length
   , h_content_length
+  , h_content_length_num
+  , h_content_length_ws
   , h_transfer_encoding
   , h_transfer_encoding
   , h_upgrade
   , h_upgrade
 
 
@@ -795,75 +793,48 @@ reexecute:
 
 
       case s_res_HTTP:
       case s_res_HTTP:
         STRICT_CHECK(ch != '/');
         STRICT_CHECK(ch != '/');
-        UPDATE_STATE(s_res_first_http_major);
+        UPDATE_STATE(s_res_http_major);
         break;
         break;
 
 
-      case s_res_first_http_major:
-        if (UNLIKELY(ch < '0' || ch > '9')) {
+      case s_res_http_major:
+        if (UNLIKELY(!IS_NUM(ch))) {
           SET_ERRNO(HPE_INVALID_VERSION);
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
           goto error;
         }
         }
 
 
         parser->http_major = ch - '0';
         parser->http_major = ch - '0';
-        UPDATE_STATE(s_res_http_major);
+        UPDATE_STATE(s_res_http_dot);
         break;
         break;
 
 
-      /* major HTTP version or dot */
-      case s_res_http_major:
+      case s_res_http_dot:
       {
       {
-        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)) {
+        if (UNLIKELY(ch != '.')) {
           SET_ERRNO(HPE_INVALID_VERSION);
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
           goto error;
         }
         }
 
 
+        UPDATE_STATE(s_res_http_minor);
         break;
         break;
       }
       }
 
 
-      /* first digit of minor HTTP version */
-      case s_res_first_http_minor:
+      case s_res_http_minor:
         if (UNLIKELY(!IS_NUM(ch))) {
         if (UNLIKELY(!IS_NUM(ch))) {
           SET_ERRNO(HPE_INVALID_VERSION);
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
           goto error;
         }
         }
 
 
         parser->http_minor = ch - '0';
         parser->http_minor = ch - '0';
-        UPDATE_STATE(s_res_http_minor);
+        UPDATE_STATE(s_res_http_end);
         break;
         break;
 
 
-      /* minor HTTP version or end of request line */
-      case s_res_http_minor:
+      case s_res_http_end:
       {
       {
-        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)) {
+        if (UNLIKELY(ch != ' ')) {
           SET_ERRNO(HPE_INVALID_VERSION);
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
           goto error;
         }
         }
 
 
+        UPDATE_STATE(s_res_first_status_code);
         break;
         break;
       }
       }
 
 
@@ -890,10 +861,9 @@ reexecute:
               UPDATE_STATE(s_res_status_start);
               UPDATE_STATE(s_res_status_start);
               break;
               break;
             case CR:
             case CR:
-              UPDATE_STATE(s_res_line_almost_done);
-              break;
             case LF:
             case LF:
-              UPDATE_STATE(s_header_field_start);
+              UPDATE_STATE(s_res_status_start);
+              REEXECUTE();
               break;
               break;
             default:
             default:
               SET_ERRNO(HPE_INVALID_STATUS);
               SET_ERRNO(HPE_INVALID_STATUS);
@@ -915,19 +885,13 @@ reexecute:
 
 
       case s_res_status_start:
       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);
         MARK(status);
         UPDATE_STATE(s_res_status);
         UPDATE_STATE(s_res_status);
         parser->index = 0;
         parser->index = 0;
+
+        if (ch == CR || ch == LF)
+          REEXECUTE();
+
         break;
         break;
       }
       }
 
 
@@ -980,7 +944,7 @@ reexecute:
             /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
             /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
             break;
             break;
           case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break;
           case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break;
-          case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break;
+          case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH, SOURCE */ break;
           case 'T': parser->method = HTTP_TRACE; break;
           case 'T': parser->method = HTTP_TRACE; break;
           case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break;
           case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break;
           default:
           default:
@@ -1007,7 +971,7 @@ reexecute:
           UPDATE_STATE(s_req_spaces_before_url);
           UPDATE_STATE(s_req_spaces_before_url);
         } else if (ch == matcher[parser->index]) {
         } else if (ch == matcher[parser->index]) {
           ; /* nada */
           ; /* nada */
-        } else if (IS_ALPHA(ch)) {
+        } else if ((ch >= 'A' && ch <= 'Z') || ch == '-') {
 
 
           switch (parser->method << 16 | parser->index << 8 | ch) {
           switch (parser->method << 16 | parser->index << 8 | ch) {
 #define XX(meth, pos, ch, new_meth) \
 #define XX(meth, pos, ch, new_meth) \
@@ -1016,31 +980,28 @@ reexecute:
 
 
             XX(POST,      1, 'U', PUT)
             XX(POST,      1, 'U', PUT)
             XX(POST,      1, 'A', PATCH)
             XX(POST,      1, 'A', PATCH)
+            XX(POST,      1, 'R', PROPFIND)
+            XX(PUT,       2, 'R', PURGE)
             XX(CONNECT,   1, 'H', CHECKOUT)
             XX(CONNECT,   1, 'H', CHECKOUT)
             XX(CONNECT,   2, 'P', COPY)
             XX(CONNECT,   2, 'P', COPY)
             XX(MKCOL,     1, 'O', MOVE)
             XX(MKCOL,     1, 'O', MOVE)
             XX(MKCOL,     1, 'E', MERGE)
             XX(MKCOL,     1, 'E', MERGE)
+            XX(MKCOL,     1, '-', MSEARCH)
             XX(MKCOL,     2, 'A', MKACTIVITY)
             XX(MKCOL,     2, 'A', MKACTIVITY)
             XX(MKCOL,     3, 'A', MKCALENDAR)
             XX(MKCOL,     3, 'A', MKCALENDAR)
             XX(SUBSCRIBE, 1, 'E', SEARCH)
             XX(SUBSCRIBE, 1, 'E', SEARCH)
+            XX(SUBSCRIBE, 1, 'O', SOURCE)
             XX(REPORT,    2, 'B', REBIND)
             XX(REPORT,    2, 'B', REBIND)
-            XX(POST,      1, 'R', PROPFIND)
             XX(PROPFIND,  4, 'P', PROPPATCH)
             XX(PROPFIND,  4, 'P', PROPPATCH)
-            XX(PUT,       2, 'R', PURGE)
             XX(LOCK,      1, 'I', LINK)
             XX(LOCK,      1, 'I', LINK)
             XX(UNLOCK,    2, 'S', UNSUBSCRIBE)
             XX(UNLOCK,    2, 'S', UNSUBSCRIBE)
             XX(UNLOCK,    2, 'B', UNBIND)
             XX(UNLOCK,    2, 'B', UNBIND)
             XX(UNLOCK,    3, 'I', UNLINK)
             XX(UNLOCK,    3, 'I', UNLINK)
 #undef XX
 #undef XX
-
             default:
             default:
               SET_ERRNO(HPE_INVALID_METHOD);
               SET_ERRNO(HPE_INVALID_METHOD);
               goto error;
               goto error;
           }
           }
-        } else if (ch == '-' &&
-                   parser->index == 1 &&
-                   parser->method == HTTP_MKCOL) {
-          parser->method = HTTP_MSEARCH;
         } else {
         } else {
           SET_ERRNO(HPE_INVALID_METHOD);
           SET_ERRNO(HPE_INVALID_METHOD);
           goto error;
           goto error;
@@ -1153,57 +1114,41 @@ reexecute:
 
 
       case s_req_http_HTTP:
       case s_req_http_HTTP:
         STRICT_CHECK(ch != '/');
         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);
         UPDATE_STATE(s_req_http_major);
         break;
         break;
 
 
-      /* major HTTP version or dot */
       case s_req_http_major:
       case s_req_http_major:
-      {
-        if (ch == '.') {
-          UPDATE_STATE(s_req_first_http_minor);
-          break;
-        }
-
         if (UNLIKELY(!IS_NUM(ch))) {
         if (UNLIKELY(!IS_NUM(ch))) {
           SET_ERRNO(HPE_INVALID_VERSION);
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
           goto error;
         }
         }
 
 
-        parser->http_major *= 10;
-        parser->http_major += ch - '0';
+        parser->http_major = ch - '0';
+        UPDATE_STATE(s_req_http_dot);
+        break;
 
 
-        if (UNLIKELY(parser->http_major > 999)) {
+      case s_req_http_dot:
+      {
+        if (UNLIKELY(ch != '.')) {
           SET_ERRNO(HPE_INVALID_VERSION);
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
           goto error;
         }
         }
 
 
+        UPDATE_STATE(s_req_http_minor);
         break;
         break;
       }
       }
 
 
-      /* first digit of minor HTTP version */
-      case s_req_first_http_minor:
+      case s_req_http_minor:
         if (UNLIKELY(!IS_NUM(ch))) {
         if (UNLIKELY(!IS_NUM(ch))) {
           SET_ERRNO(HPE_INVALID_VERSION);
           SET_ERRNO(HPE_INVALID_VERSION);
           goto error;
           goto error;
         }
         }
 
 
         parser->http_minor = ch - '0';
         parser->http_minor = ch - '0';
-        UPDATE_STATE(s_req_http_minor);
+        UPDATE_STATE(s_req_http_end);
         break;
         break;
 
 
-      /* minor HTTP version or end of request line */
-      case s_req_http_minor:
+      case s_req_http_end:
       {
       {
         if (ch == CR) {
         if (ch == CR) {
           UPDATE_STATE(s_req_line_almost_done);
           UPDATE_STATE(s_req_line_almost_done);
@@ -1215,21 +1160,8 @@ reexecute:
           break;
           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;
-        }
-
+        SET_ERRNO(HPE_INVALID_VERSION);
+        goto error;
         break;
         break;
       }
       }
 
 
@@ -1476,6 +1408,7 @@ reexecute:
 
 
             parser->flags |= F_CONTENTLENGTH;
             parser->flags |= F_CONTENTLENGTH;
             parser->content_length = ch - '0';
             parser->content_length = ch - '0';
+            parser->header_state = h_content_length_num;
             break;
             break;
 
 
           case h_connection:
           case h_connection:
@@ -1563,10 +1496,18 @@ reexecute:
               break;
               break;
 
 
             case h_content_length:
             case h_content_length:
+              if (ch == ' ') break;
+              h_state = h_content_length_num;
+              /* FALLTHROUGH */
+
+            case h_content_length_num:
             {
             {
               uint64_t t;
               uint64_t t;
 
 
-              if (ch == ' ') break;
+              if (ch == ' ') {
+                h_state = h_content_length_ws;
+                break;
+              }
 
 
               if (UNLIKELY(!IS_NUM(ch))) {
               if (UNLIKELY(!IS_NUM(ch))) {
                 SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
                 SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
@@ -1589,6 +1530,12 @@ reexecute:
               break;
               break;
             }
             }
 
 
+            case h_content_length_ws:
+              if (ch == ' ') break;
+              SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+              parser->header_state = h_state;
+              goto error;
+
             /* Transfer-Encoding: chunked */
             /* Transfer-Encoding: chunked */
             case h_matching_transfer_encoding_chunked:
             case h_matching_transfer_encoding_chunked:
               parser->index++;
               parser->index++;
@@ -1794,10 +1741,17 @@ reexecute:
         UPDATE_STATE(s_headers_done);
         UPDATE_STATE(s_headers_done);
 
 
         /* Set this here so that on_headers_complete() callbacks can see it */
         /* 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);
+        if ((parser->flags & F_UPGRADE) &&
+            (parser->flags & F_CONNECTION_UPGRADE)) {
+          /* For responses, "Upgrade: foo" and "Connection: upgrade" are
+           * mandatory only when it is a 101 Switching Protocols response,
+           * otherwise it is purely informational, to announce support.
+           */
+          parser->upgrade =
+              (parser->type == HTTP_REQUEST || parser->status_code == 101);
+        } else {
+          parser->upgrade = (parser->method == HTTP_CONNECT);
+        }
 
 
         /* Here we call the headers_complete callback. This is somewhat
         /* Here we call the headers_complete callback. This is somewhat
          * different than other callbacks because if the user returns 1, we
          * different than other callbacks because if the user returns 1, we
@@ -1816,6 +1770,7 @@ reexecute:
             case 2:
             case 2:
               parser->upgrade = 1;
               parser->upgrade = 1;
 
 
+            /* FALLTHROUGH */
             case 1:
             case 1:
               parser->flags |= F_SKIPBODY;
               parser->flags |= F_SKIPBODY;
               break;
               break;
@@ -2375,7 +2330,7 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
       case s_req_server_with_at:
       case s_req_server_with_at:
         found_at = 1;
         found_at = 1;
 
 
-      /* FALLTROUGH */
+      /* FALLTHROUGH */
       case s_req_server:
       case s_req_server:
         uf = UF_HOST;
         uf = UF_HOST;
         break;
         break;
@@ -2429,12 +2384,27 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
   }
   }
 
 
   if (u->field_set & (1 << UF_PORT)) {
   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;
+    uint16_t off;
+    uint16_t len;
+    const char* p;
+    const char* end;
+    unsigned long v;
+
+    off = u->field_data[UF_PORT].off;
+    len = u->field_data[UF_PORT].len;
+    end = buf + off + len;
+
+    /* NOTE: The characters are already validated and are in the [0-9] range */
+    assert(off + len <= buflen && "Port number overflow");
+    v = 0;
+    for (p = buf + off; p < end; p++) {
+      v *= 10;
+      v += *p - '0';
+
+      /* Ports have a max value of 2^16 */
+      if (v > 0xffff) {
+        return 1;
+      }
     }
     }
 
 
     u->port = (uint16_t) v;
     u->port = (uint16_t) v;

+ 74 - 3
http_parser.h

@@ -26,14 +26,13 @@ extern "C" {
 
 
 /* Also update SONAME in the Makefile whenever you change these. */
 /* Also update SONAME in the Makefile whenever you change these. */
 #define HTTP_PARSER_VERSION_MAJOR 2
 #define HTTP_PARSER_VERSION_MAJOR 2
-#define HTTP_PARSER_VERSION_MINOR 7
+#define HTTP_PARSER_VERSION_MINOR 8
 #define HTTP_PARSER_VERSION_PATCH 1
 #define HTTP_PARSER_VERSION_PATCH 1
 
 
-#include <sys/types.h>
+#include <stddef.h>
 #if defined(_WIN32) && !defined(__MINGW32__) && \
 #if defined(_WIN32) && !defined(__MINGW32__) && \
   (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
   (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
 #include <BaseTsd.h>
 #include <BaseTsd.h>
-#include <stddef.h>
 typedef __int8 int8_t;
 typedef __int8 int8_t;
 typedef unsigned __int8 uint8_t;
 typedef unsigned __int8 uint8_t;
 typedef __int16 int16_t;
 typedef __int16 int16_t;
@@ -90,6 +89,76 @@ typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
 typedef int (*http_cb) (http_parser*);
 typedef int (*http_cb) (http_parser*);
 
 
 
 
+/* Status Codes */
+#define HTTP_STATUS_MAP(XX)                                                 \
+  XX(100, CONTINUE,                        Continue)                        \
+  XX(101, SWITCHING_PROTOCOLS,             Switching Protocols)             \
+  XX(102, PROCESSING,                      Processing)                      \
+  XX(200, OK,                              OK)                              \
+  XX(201, CREATED,                         Created)                         \
+  XX(202, ACCEPTED,                        Accepted)                        \
+  XX(203, NON_AUTHORITATIVE_INFORMATION,   Non-Authoritative Information)   \
+  XX(204, NO_CONTENT,                      No Content)                      \
+  XX(205, RESET_CONTENT,                   Reset Content)                   \
+  XX(206, PARTIAL_CONTENT,                 Partial Content)                 \
+  XX(207, MULTI_STATUS,                    Multi-Status)                    \
+  XX(208, ALREADY_REPORTED,                Already Reported)                \
+  XX(226, IM_USED,                         IM Used)                         \
+  XX(300, MULTIPLE_CHOICES,                Multiple Choices)                \
+  XX(301, MOVED_PERMANENTLY,               Moved Permanently)               \
+  XX(302, FOUND,                           Found)                           \
+  XX(303, SEE_OTHER,                       See Other)                       \
+  XX(304, NOT_MODIFIED,                    Not Modified)                    \
+  XX(305, USE_PROXY,                       Use Proxy)                       \
+  XX(307, TEMPORARY_REDIRECT,              Temporary Redirect)              \
+  XX(308, PERMANENT_REDIRECT,              Permanent Redirect)              \
+  XX(400, BAD_REQUEST,                     Bad Request)                     \
+  XX(401, UNAUTHORIZED,                    Unauthorized)                    \
+  XX(402, PAYMENT_REQUIRED,                Payment Required)                \
+  XX(403, FORBIDDEN,                       Forbidden)                       \
+  XX(404, NOT_FOUND,                       Not Found)                       \
+  XX(405, METHOD_NOT_ALLOWED,              Method Not Allowed)              \
+  XX(406, NOT_ACCEPTABLE,                  Not Acceptable)                  \
+  XX(407, PROXY_AUTHENTICATION_REQUIRED,   Proxy Authentication Required)   \
+  XX(408, REQUEST_TIMEOUT,                 Request Timeout)                 \
+  XX(409, CONFLICT,                        Conflict)                        \
+  XX(410, GONE,                            Gone)                            \
+  XX(411, LENGTH_REQUIRED,                 Length Required)                 \
+  XX(412, PRECONDITION_FAILED,             Precondition Failed)             \
+  XX(413, PAYLOAD_TOO_LARGE,               Payload Too Large)               \
+  XX(414, URI_TOO_LONG,                    URI Too Long)                    \
+  XX(415, UNSUPPORTED_MEDIA_TYPE,          Unsupported Media Type)          \
+  XX(416, RANGE_NOT_SATISFIABLE,           Range Not Satisfiable)           \
+  XX(417, EXPECTATION_FAILED,              Expectation Failed)              \
+  XX(421, MISDIRECTED_REQUEST,             Misdirected Request)             \
+  XX(422, UNPROCESSABLE_ENTITY,            Unprocessable Entity)            \
+  XX(423, LOCKED,                          Locked)                          \
+  XX(424, FAILED_DEPENDENCY,               Failed Dependency)               \
+  XX(426, UPGRADE_REQUIRED,                Upgrade Required)                \
+  XX(428, PRECONDITION_REQUIRED,           Precondition Required)           \
+  XX(429, TOO_MANY_REQUESTS,               Too Many Requests)               \
+  XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \
+  XX(451, UNAVAILABLE_FOR_LEGAL_REASONS,   Unavailable For Legal Reasons)   \
+  XX(500, INTERNAL_SERVER_ERROR,           Internal Server Error)           \
+  XX(501, NOT_IMPLEMENTED,                 Not Implemented)                 \
+  XX(502, BAD_GATEWAY,                     Bad Gateway)                     \
+  XX(503, SERVICE_UNAVAILABLE,             Service Unavailable)             \
+  XX(504, GATEWAY_TIMEOUT,                 Gateway Timeout)                 \
+  XX(505, HTTP_VERSION_NOT_SUPPORTED,      HTTP Version Not Supported)      \
+  XX(506, VARIANT_ALSO_NEGOTIATES,         Variant Also Negotiates)         \
+  XX(507, INSUFFICIENT_STORAGE,            Insufficient Storage)            \
+  XX(508, LOOP_DETECTED,                   Loop Detected)                   \
+  XX(510, NOT_EXTENDED,                    Not Extended)                    \
+  XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \
+
+enum http_status
+  {
+#define XX(num, name, string) HTTP_STATUS_##name = num,
+  HTTP_STATUS_MAP(XX)
+#undef XX
+  };
+
+
 /* Request Methods */
 /* Request Methods */
 #define HTTP_METHOD_MAP(XX)         \
 #define HTTP_METHOD_MAP(XX)         \
   XX(0,  DELETE,      DELETE)       \
   XX(0,  DELETE,      DELETE)       \
@@ -132,6 +201,8 @@ typedef int (*http_cb) (http_parser*);
   /* RFC-2068, section 19.6.1.2 */  \
   /* RFC-2068, section 19.6.1.2 */  \
   XX(31, LINK,        LINK)         \
   XX(31, LINK,        LINK)         \
   XX(32, UNLINK,      UNLINK)       \
   XX(32, UNLINK,      UNLINK)       \
+  /* icecast */                     \
+  XX(33, SOURCE,      SOURCE)       \
 
 
 enum http_method
 enum http_method
   {
   {

+ 267 - 37
test.c

@@ -27,9 +27,7 @@
 #include <stdarg.h>
 #include <stdarg.h>
 
 
 #if defined(__APPLE__)
 #if defined(__APPLE__)
-# undef strlcat
 # undef strlncpy
 # undef strlncpy
-# undef strlcpy
 #endif  /* defined(__APPLE__) */
 #endif  /* defined(__APPLE__) */
 
 
 #undef TRUE
 #undef TRUE
@@ -43,6 +41,8 @@
 
 
 #define MIN(a,b) ((a) < (b) ? (a) : (b))
 #define MIN(a,b) ((a) < (b) ? (a) : (b))
 
 
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x))
+
 static http_parser *parser;
 static http_parser *parser;
 
 
 struct message {
 struct message {
@@ -78,6 +78,7 @@ struct message {
   int message_begin_cb_called;
   int message_begin_cb_called;
   int headers_complete_cb_called;
   int headers_complete_cb_called;
   int message_complete_cb_called;
   int message_complete_cb_called;
+  int status_cb_called;
   int message_complete_on_eof;
   int message_complete_on_eof;
   int body_is_final;
   int body_is_final;
 };
 };
@@ -1131,7 +1132,7 @@ const struct message requests[] =
   }
   }
 
 
 #define UNLINK_REQUEST 41
 #define UNLINK_REQUEST 41
-, {.name = "link request"
+, {.name = "unlink request"
   ,.type= HTTP_REQUEST
   ,.type= HTTP_REQUEST
   ,.raw= "UNLINK /images/my_dog.jpg HTTP/1.1\r\n"
   ,.raw= "UNLINK /images/my_dog.jpg HTTP/1.1\r\n"
          "Host: example.com\r\n"
          "Host: example.com\r\n"
@@ -1153,7 +1154,25 @@ const struct message requests[] =
   ,.body= ""
   ,.body= ""
   }
   }
 
 
-, {.name= NULL } /* sentinel */
+#define SOURCE_REQUEST 42
+, {.name = "source request"
+  ,.type= HTTP_REQUEST
+  ,.raw= "SOURCE /music/sweet/music HTTP/1.1\r\n"
+         "Host: example.com\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_SOURCE
+  ,.request_path= "/music/sweet/music"
+  ,.request_url= "/music/sweet/music"
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.num_headers= 1
+  ,.headers= { { "Host", "example.com" } }
+  ,.body= ""
+  }
 };
 };
 
 
 /* * R E S P O N S E S * */
 /* * R E S P O N S E S * */
@@ -1771,7 +1790,166 @@ const struct message responses[] =
   ,.chunk_lengths= { 2 }
   ,.chunk_lengths= { 2 }
   }
   }
 
 
-, {.name= NULL } /* sentinel */
+#define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER 22
+, {.name= "HTTP 101 response with Upgrade header"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 101 Switching Protocols\r\n"
+         "Connection: upgrade\r\n"
+         "Upgrade: h2c\r\n"
+         "\r\n"
+         "proto"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 101
+  ,.response_status= "Switching Protocols"
+  ,.upgrade= "proto"
+  ,.num_headers= 2
+  ,.headers=
+    { { "Connection", "upgrade" }
+    , { "Upgrade", "h2c" }
+    }
+  }
+
+#define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER_AND_CONTENT_LENGTH 23
+, {.name= "HTTP 101 response with Upgrade and Content-Length header"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 101 Switching Protocols\r\n"
+         "Connection: upgrade\r\n"
+         "Upgrade: h2c\r\n"
+         "Content-Length: 4\r\n"
+         "\r\n"
+         "body"
+         "proto"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 101
+  ,.response_status= "Switching Protocols"
+  ,.body= "body"
+  ,.upgrade= "proto"
+  ,.num_headers= 3
+  ,.headers=
+    { { "Connection", "upgrade" }
+    , { "Upgrade", "h2c" }
+    , { "Content-Length", "4" }
+    }
+  }
+
+#define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER_AND_TRANSFER_ENCODING 24
+, {.name= "HTTP 101 response with Upgrade and Transfer-Encoding header"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 101 Switching Protocols\r\n"
+         "Connection: upgrade\r\n"
+         "Upgrade: h2c\r\n"
+         "Transfer-Encoding: chunked\r\n"
+         "\r\n"
+         "2\r\n"
+         "bo\r\n"
+         "2\r\n"
+         "dy\r\n"
+         "0\r\n"
+         "\r\n"
+         "proto"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 101
+  ,.response_status= "Switching Protocols"
+  ,.body= "body"
+  ,.upgrade= "proto"
+  ,.num_headers= 3
+  ,.headers=
+    { { "Connection", "upgrade" }
+    , { "Upgrade", "h2c" }
+    , { "Transfer-Encoding", "chunked" }
+    }
+  ,.num_chunks_complete= 3
+  ,.chunk_lengths= { 2, 2 }
+  }
+
+#define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER 25
+, {.name= "HTTP 200 response with Upgrade header"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 200 OK\r\n"
+         "Connection: upgrade\r\n"
+         "Upgrade: h2c\r\n"
+         "\r\n"
+         "body"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= TRUE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 200
+  ,.response_status= "OK"
+  ,.body= "body"
+  ,.upgrade= NULL
+  ,.num_headers= 2
+  ,.headers=
+    { { "Connection", "upgrade" }
+    , { "Upgrade", "h2c" }
+    }
+  }
+
+#define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER_AND_CONTENT_LENGTH 26
+, {.name= "HTTP 200 response with Upgrade and Content-Length header"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 200 OK\r\n"
+         "Connection: upgrade\r\n"
+         "Upgrade: h2c\r\n"
+         "Content-Length: 4\r\n"
+         "\r\n"
+         "body"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 200
+  ,.response_status= "OK"
+  ,.num_headers= 3
+  ,.body= "body"
+  ,.upgrade= NULL
+  ,.headers=
+    { { "Connection", "upgrade" }
+    , { "Upgrade", "h2c" }
+    , { "Content-Length", "4" }
+    }
+  }
+
+#define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER_AND_TRANSFER_ENCODING 27
+, {.name= "HTTP 200 response with Upgrade and Transfer-Encoding header"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 200 OK\r\n"
+         "Connection: upgrade\r\n"
+         "Upgrade: h2c\r\n"
+         "Transfer-Encoding: chunked\r\n"
+         "\r\n"
+         "2\r\n"
+         "bo\r\n"
+         "2\r\n"
+         "dy\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= 3
+  ,.body= "body"
+  ,.upgrade= NULL
+  ,.headers=
+    { { "Connection", "upgrade" }
+    , { "Upgrade", "h2c" }
+    , { "Transfer-Encoding", "chunked" }
+    }
+  ,.num_chunks_complete= 3
+  ,.chunk_lengths= { 2, 2 }
+  }
 };
 };
 
 
 /* strnlen() is a POSIX.2008 addition. Can't rely on it being available so
 /* strnlen() is a POSIX.2008 addition. Can't rely on it being available so
@@ -1812,12 +1990,6 @@ strlncat(char *dst, size_t len, const char *src, size_t n)
 }
 }
 
 
 size_t
 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)
 strlncpy(char *dst, size_t len, const char *src, size_t n)
 {
 {
   size_t slen;
   size_t slen;
@@ -1835,12 +2007,6 @@ strlncpy(char *dst, size_t len, const char *src, size_t n)
   return slen;
   return slen;
 }
 }
 
 
-size_t
-strlcpy(char *dst, const char *src, size_t len)
-{
-  return strlncpy(dst, len, src, (size_t) -1);
-}
-
 int
 int
 request_url_cb (http_parser *p, const char *buf, size_t len)
 request_url_cb (http_parser *p, const char *buf, size_t len)
 {
 {
@@ -1981,6 +2147,9 @@ int
 response_status_cb (http_parser *p, const char *buf, size_t len)
 response_status_cb (http_parser *p, const char *buf, size_t len)
 {
 {
   assert(p == parser);
   assert(p == parser);
+
+  messages[num_messages].status_cb_called = TRUE;
+
   strlncat(messages[num_messages].response_status,
   strlncat(messages[num_messages].response_status,
            sizeof(messages[num_messages].response_status),
            sizeof(messages[num_messages].response_status),
            buf,
            buf,
@@ -2405,6 +2574,7 @@ message_eq (int index, int connect, const struct message *expected)
   } else {
   } else {
     MESSAGE_CHECK_NUM_EQ(expected, m, status_code);
     MESSAGE_CHECK_NUM_EQ(expected, m, status_code);
     MESSAGE_CHECK_STR_EQ(expected, m, response_status);
     MESSAGE_CHECK_STR_EQ(expected, m, response_status);
+    assert(m->status_cb_called);
   }
   }
 
 
   if (!connect) {
   if (!connect) {
@@ -2476,7 +2646,9 @@ message_eq (int index, int connect, const struct message *expected)
     if (!r) return 0;
     if (!r) return 0;
   }
   }
 
 
-  MESSAGE_CHECK_STR_EQ(expected, m, upgrade);
+  if (!connect) {
+    MESSAGE_CHECK_STR_EQ(expected, m, upgrade);
+  }
 
 
   return 1;
   return 1;
 }
 }
@@ -3313,9 +3485,11 @@ test_message_count_body (const struct message *message)
 }
 }
 
 
 void
 void
-test_simple (const char *buf, enum http_errno err_expected)
+test_simple_type (const char *buf,
+                  enum http_errno err_expected,
+                  enum http_parser_type type)
 {
 {
-  parser_init(HTTP_REQUEST);
+  parser_init(type);
 
 
   enum http_errno err;
   enum http_errno err;
 
 
@@ -3340,6 +3514,12 @@ test_simple (const char *buf, enum http_errno err_expected)
 }
 }
 
 
 void
 void
+test_simple (const char *buf, enum http_errno err_expected)
+{
+  test_simple_type(buf, err_expected, HTTP_REQUEST);
+}
+
+void
 test_invalid_header_content (int req, const char* str)
 test_invalid_header_content (int req, const char* str)
 {
 {
   http_parser parser;
   http_parser parser;
@@ -3489,6 +3669,30 @@ test_header_cr_no_lf_error (int req)
 }
 }
 
 
 void
 void
+test_no_overflow_parse_url (void)
+{
+  int rv;
+  struct http_parser_url u;
+
+  http_parser_url_init(&u);
+  rv = http_parser_parse_url("http://example.com:8001", 22, 0, &u);
+
+  if (rv != 0) {
+    fprintf(stderr,
+            "\n*** test_no_overflow_parse_url invalid return value=%d\n",
+            rv);
+    abort();
+  }
+
+  if (u.port != 800) {
+    fprintf(stderr,
+            "\n*** test_no_overflow_parse_url invalid port number=%d\n",
+            u.port);
+    abort();
+  }
+}
+
+void
 test_header_overflow_error (int req)
 test_header_overflow_error (int req)
 {
 {
   http_parser parser;
   http_parser parser;
@@ -3895,9 +4099,7 @@ int
 main (void)
 main (void)
 {
 {
   parser = NULL;
   parser = NULL;
-  int i, j, k;
-  int request_count;
-  int response_count;
+  unsigned i, j, k;
   unsigned long version;
   unsigned long version;
   unsigned major;
   unsigned major;
   unsigned minor;
   unsigned minor;
@@ -3911,9 +4113,6 @@ main (void)
 
 
   printf("sizeof(http_parser) = %u\n", (unsigned int)sizeof(http_parser));
   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
   //// API
   test_preserve_data();
   test_preserve_data();
   test_parse_url();
   test_parse_url();
@@ -3923,6 +4122,7 @@ main (void)
   test_header_nread_value();
   test_header_nread_value();
 
 
   //// OVERFLOW CONDITIONS
   //// OVERFLOW CONDITIONS
+  test_no_overflow_parse_url();
 
 
   test_header_overflow_error(HTTP_REQUEST);
   test_header_overflow_error(HTTP_REQUEST);
   test_no_overflow_long_body(HTTP_REQUEST, 1000);
   test_no_overflow_long_body(HTTP_REQUEST, 1000);
@@ -3947,25 +4147,52 @@ main (void)
   test_invalid_header_field_token_error(HTTP_RESPONSE);
   test_invalid_header_field_token_error(HTTP_RESPONSE);
   test_invalid_header_field_content_error(HTTP_RESPONSE);
   test_invalid_header_field_content_error(HTTP_RESPONSE);
 
 
+  test_simple_type(
+      "POST / HTTP/1.1\r\n"
+      "Content-Length:  42 \r\n"  // Note the surrounding whitespace.
+      "\r\n",
+      HPE_OK,
+      HTTP_REQUEST);
+
+  test_simple_type(
+      "POST / HTTP/1.1\r\n"
+      "Content-Length: 4 2\r\n"
+      "\r\n",
+      HPE_INVALID_CONTENT_LENGTH,
+      HTTP_REQUEST);
+
+  test_simple_type(
+      "POST / HTTP/1.1\r\n"
+      "Content-Length: 13 37\r\n"
+      "\r\n",
+      HPE_INVALID_CONTENT_LENGTH,
+      HTTP_REQUEST);
+
   //// RESPONSES
   //// RESPONSES
 
 
-  for (i = 0; i < response_count; i++) {
+  test_simple_type("HTP/1.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
+  test_simple_type("HTTP/01.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
+  test_simple_type("HTTP/11.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
+  test_simple_type("HTTP/1.01 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
+  test_simple_type("HTTP/1.1\t200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
+
+  for (i = 0; i < ARRAY_SIZE(responses); i++) {
     test_message(&responses[i]);
     test_message(&responses[i]);
   }
   }
 
 
-  for (i = 0; i < response_count; i++) {
+  for (i = 0; i < ARRAY_SIZE(responses); i++) {
     test_message_pause(&responses[i]);
     test_message_pause(&responses[i]);
   }
   }
 
 
-  for (i = 0; i < response_count; i++) {
+  for (i = 0; i < ARRAY_SIZE(responses); i++) {
     test_message_connect(&responses[i]);
     test_message_connect(&responses[i]);
   }
   }
 
 
-  for (i = 0; i < response_count; i++) {
+  for (i = 0; i < ARRAY_SIZE(responses); i++) {
     if (!responses[i].should_keep_alive) continue;
     if (!responses[i].should_keep_alive) continue;
-    for (j = 0; j < response_count; j++) {
+    for (j = 0; j < ARRAY_SIZE(responses); j++) {
       if (!responses[j].should_keep_alive) continue;
       if (!responses[j].should_keep_alive) continue;
-      for (k = 0; k < response_count; k++) {
+      for (k = 0; k < ARRAY_SIZE(responses); k++) {
         test_multiple3(&responses[i], &responses[j], &responses[k]);
         test_multiple3(&responses[i], &responses[j], &responses[k]);
       }
       }
     }
     }
@@ -4026,6 +4253,9 @@ main (void)
   /// REQUESTS
   /// REQUESTS
 
 
   test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION);
   test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION);
+  test_simple("GET / HTTP/01.1\r\n\r\n", HPE_INVALID_VERSION);
+  test_simple("GET / HTTP/11.1\r\n\r\n", HPE_INVALID_VERSION);
+  test_simple("GET / HTTP/1.01\r\n\r\n", HPE_INVALID_VERSION);
 
 
   // Extended characters - see nodejs/test/parallel/test-http-headers-obstext.js
   // Extended characters - see nodejs/test/parallel/test-http-headers-obstext.js
   test_simple("GET / HTTP/1.1\r\n"
   test_simple("GET / HTTP/1.1\r\n"
@@ -4178,19 +4408,19 @@ main (void)
 
 
 
 
   /* check to make sure our predefined requests are okay */
   /* check to make sure our predefined requests are okay */
-  for (i = 0; requests[i].name; i++) {
+  for (i = 0; i < ARRAY_SIZE(requests); i++) {
     test_message(&requests[i]);
     test_message(&requests[i]);
   }
   }
 
 
-  for (i = 0; i < request_count; i++) {
+  for (i = 0; i < ARRAY_SIZE(requests); i++) {
     test_message_pause(&requests[i]);
     test_message_pause(&requests[i]);
   }
   }
 
 
-  for (i = 0; i < request_count; i++) {
+  for (i = 0; i < ARRAY_SIZE(requests); i++) {
     if (!requests[i].should_keep_alive) continue;
     if (!requests[i].should_keep_alive) continue;
-    for (j = 0; j < request_count; j++) {
+    for (j = 0; j < ARRAY_SIZE(requests); j++) {
       if (!requests[j].should_keep_alive) continue;
       if (!requests[j].should_keep_alive) continue;
-      for (k = 0; k < request_count; k++) {
+      for (k = 0; k < ARRAY_SIZE(requests); k++) {
         test_multiple3(&requests[i], &requests[j], &requests[k]);
         test_multiple3(&requests[i], &requests[j], &requests[k]);
       }
       }
     }
     }