Browse Source

Import upstream version 2.9.2

Ben Noordhuis 3 years ago
parent
commit
ba7ec83342
4 changed files with 278 additions and 152 deletions
  1. 2 2
      Makefile
  2. 122 64
      http_parser.c
  3. 8 2
      http_parser.h
  4. 146 84
      test.c

+ 2 - 2
Makefile

@@ -23,8 +23,8 @@ HELPER ?=
 BINEXT ?=
 SOLIBNAME = libhttp_parser
 SOMAJOR = 2
-SOMINOR = 8
-SOREV   = 1
+SOMINOR = 9
+SOREV   = 2
 ifeq (darwin,$(PLATFORM))
 SOEXT ?= dylib
 SONAME ?= $(SOLIBNAME).$(SOMAJOR).$(SOMINOR).$(SOEXT)

+ 122 - 64
http_parser.c

@@ -25,6 +25,8 @@
 #include <string.h>
 #include <limits.h>
 
+static uint32_t max_header_size = HTTP_MAX_HEADER_SIZE;
+
 #ifndef ULLONG_MAX
 # define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */
 #endif
@@ -49,6 +51,7 @@
 
 #define SET_ERRNO(e)                                                 \
 do {                                                                 \
+  parser->nread = nread;                                             \
   parser->http_errno = (e);                                          \
 } while(0)
 
@@ -56,6 +59,7 @@ do {                                                                 \
 #define UPDATE_STATE(V) p_state = (enum state) (V);
 #define RETURN(V)                                                    \
 do {                                                                 \
+  parser->nread = nread;                                             \
   parser->state = CURRENT_STATE();                                   \
   return (V);                                                        \
 } while (0);
@@ -137,20 +141,20 @@ 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
+ * line) to exceed 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
+ * make the web a little safer.  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))) {            \
+  nread += (uint32_t)(V);                                            \
+  if (UNLIKELY(nread > max_header_size)) {                           \
     SET_ERRNO(HPE_HEADER_OVERFLOW);                                  \
     goto error;                                                      \
   }                                                                  \
@@ -192,7 +196,7 @@ static const char tokens[256] = {
 /*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */
         0,       0,       0,       0,       0,       0,       0,       0,
 /*  32 sp    33  !    34  "    35  #    36  $    37  %    38  &    39  '  */
-        0,      '!',      0,      '#',     '$',     '%',     '&',    '\'',
+       ' ',     '!',      0,      '#',     '$',     '%',     '&',    '\'',
 /*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */
         0,       0,      '*',     '+',      0,      '-',     '.',      0,
 /*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */
@@ -312,6 +316,8 @@ enum state
   , s_req_http_HT
   , s_req_http_HTT
   , s_req_http_HTTP
+  , s_req_http_I
+  , s_req_http_IC
   , s_req_http_major
   , s_req_http_dot
   , s_req_http_minor
@@ -419,14 +425,14 @@ enum http_host_state
   (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
   (c) == '$' || (c) == ',')
 
-#define STRICT_TOKEN(c)     (tokens[(unsigned char)c])
+#define STRICT_TOKEN(c)     ((c == ' ') ? 0 : tokens[(unsigned char)c])
 
 #if HTTP_PARSER_STRICT
-#define TOKEN(c)            (tokens[(unsigned char)c])
+#define TOKEN(c)            STRICT_TOKEN(c)
 #define IS_URL_CHAR(c)      (BIT_AT(normal_url_char, (unsigned char)c))
 #define IS_HOST_CHAR(c)     (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
 #else
-#define TOKEN(c)            ((c == ' ') ? ' ' : tokens[(unsigned char)c])
+#define TOKEN(c)            tokens[(unsigned char)c]
 #define IS_URL_CHAR(c)                                                         \
   (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))
 #define IS_HOST_CHAR(c)                                                        \
@@ -540,7 +546,7 @@ parse_url_char(enum state s, const char ch)
         return s_dead;
       }
 
-    /* FALLTHROUGH */
+    /* fall through */
     case s_req_server_start:
     case s_req_server:
       if (ch == '/') {
@@ -644,6 +650,7 @@ size_t http_parser_execute (http_parser *parser,
   const char *status_mark = 0;
   enum state p_state = (enum state) parser->state;
   const unsigned int lenient = parser->lenient_http_headers;
+  uint32_t nread = parser->nread;
 
   /* We're in an error state. Don't bother doing anything. */
   if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
@@ -755,21 +762,16 @@ reexecute:
 
       case s_start_res:
       {
+        if (ch == CR || ch == LF)
+          break;
         parser->flags = 0;
         parser->content_length = ULLONG_MAX;
 
-        switch (ch) {
-          case 'H':
-            UPDATE_STATE(s_res_H);
-            break;
-
-          case CR:
-          case LF:
-            break;
-
-          default:
-            SET_ERRNO(HPE_INVALID_CONSTANT);
-            goto error;
+        if (ch == 'H') {
+          UPDATE_STATE(s_res_H);
+        } else {
+          SET_ERRNO(HPE_INVALID_CONSTANT);
+          goto error;
         }
 
         CALLBACK_NOTIFY(message_begin);
@@ -1086,11 +1088,17 @@ reexecute:
 
       case s_req_http_start:
         switch (ch) {
+          case ' ':
+            break;
           case 'H':
             UPDATE_STATE(s_req_http_H);
             break;
-          case ' ':
-            break;
+          case 'I':
+            if (parser->method == HTTP_SOURCE) {
+              UPDATE_STATE(s_req_http_I);
+              break;
+            }
+            /* fall through */
           default:
             SET_ERRNO(HPE_INVALID_CONSTANT);
             goto error;
@@ -1112,6 +1120,16 @@ reexecute:
         UPDATE_STATE(s_req_http_HTTP);
         break;
 
+      case s_req_http_I:
+        STRICT_CHECK(ch != 'C');
+        UPDATE_STATE(s_req_http_IC);
+        break;
+
+      case s_req_http_IC:
+        STRICT_CHECK(ch != 'E');
+        UPDATE_STATE(s_req_http_HTTP);  /* Treat "ICE" as "HTTP". */
+        break;
+
       case s_req_http_HTTP:
         STRICT_CHECK(ch != '/');
         UPDATE_STATE(s_req_http_major);
@@ -1238,8 +1256,14 @@ reexecute:
             break;
 
           switch (parser->header_state) {
-            case h_general:
+            case h_general: {
+              size_t left = data + len - p;
+              const char* pe = p + MIN(left, max_header_size);
+              while (p+1 < pe && TOKEN(p[1])) {
+                p++;
+              }
               break;
+            }
 
             case h_C:
               parser->index++;
@@ -1339,13 +1363,14 @@ reexecute:
           }
         }
 
-        COUNT_HEADER_SIZE(p - start);
-
         if (p == data + len) {
           --p;
+          COUNT_HEADER_SIZE(p - start);
           break;
         }
 
+        COUNT_HEADER_SIZE(p - start);
+
         if (ch == ':') {
           UPDATE_STATE(s_header_value_discard_ws);
           CALLBACK_DATA(header_field);
@@ -1369,7 +1394,7 @@ reexecute:
           break;
         }
 
-        /* FALLTHROUGH */
+        /* fall through */
 
       case s_header_value_start:
       {
@@ -1411,6 +1436,11 @@ reexecute:
             parser->header_state = h_content_length_num;
             break;
 
+          /* when obsolete line folding is encountered for content length
+           * continue to the s_header_value state */
+          case h_content_length_ws:
+            break;
+
           case h_connection:
             /* looking for 'Connection: keep-alive' */
             if (c == 'k') {
@@ -1466,29 +1496,25 @@ reexecute:
 
           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;
+              {
+                size_t left = data + len - p;
+                const char* pe = p + MIN(left, max_header_size);
+
+                for (; p != pe; p++) {
+                  ch = *p;
+                  if (ch == CR || ch == LF) {
+                    --p;
+                    break;
+                  }
+                  if (!lenient && !IS_HEADER_CHAR(ch)) {
+                    SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+                    goto error;
+                  }
+                }
+                if (p == data + len)
+                  --p;
+                break;
               }
-              --p;
-
-              break;
-            }
 
             case h_connection:
             case h_transfer_encoding:
@@ -1498,7 +1524,7 @@ reexecute:
             case h_content_length:
               if (ch == ' ') break;
               h_state = h_content_length_num;
-              /* FALLTHROUGH */
+              /* fall through */
 
             case h_content_length_num:
             {
@@ -1634,10 +1660,10 @@ reexecute:
         }
         parser->header_state = h_state;
 
-        COUNT_HEADER_SIZE(p - start);
-
         if (p == data + len)
           --p;
+
+        COUNT_HEADER_SIZE(p - start);
         break;
       }
 
@@ -1655,6 +1681,10 @@ reexecute:
       case s_header_value_lws:
       {
         if (ch == ' ' || ch == '\t') {
+          if (parser->header_state == h_content_length_num) {
+              /* treat obsolete line folding as space */
+              parser->header_state = h_content_length_ws;
+          }
           UPDATE_STATE(s_header_value_start);
           REEXECUTE();
         }
@@ -1707,6 +1737,11 @@ reexecute:
             case h_transfer_encoding_chunked:
               parser->flags |= F_CHUNKED;
               break;
+            case h_content_length:
+              /* do not allow empty content length */
+              SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+              goto error;
+              break;
             default:
               break;
           }
@@ -1770,7 +1805,7 @@ reexecute:
             case 2:
               parser->upgrade = 1;
 
-            /* FALLTHROUGH */
+              /* fall through */
             case 1:
               parser->flags |= F_SKIPBODY;
               break;
@@ -1794,6 +1829,7 @@ reexecute:
         STRICT_CHECK(ch != LF);
 
         parser->nread = 0;
+        nread = 0;
 
         hasBody = parser->flags & F_CHUNKED ||
           (parser->content_length > 0 && parser->content_length != ULLONG_MAX);
@@ -1888,7 +1924,7 @@ reexecute:
 
       case s_chunk_size_start:
       {
-        assert(parser->nread == 1);
+        assert(nread == 1);
         assert(parser->flags & F_CHUNKED);
 
         unhex_val = unhex[(unsigned char)ch];
@@ -1956,6 +1992,7 @@ reexecute:
         STRICT_CHECK(ch != LF);
 
         parser->nread = 0;
+        nread = 0;
 
         if (parser->content_length == 0) {
           parser->flags |= F_TRAILING;
@@ -2002,6 +2039,7 @@ reexecute:
         assert(parser->flags & F_CHUNKED);
         STRICT_CHECK(ch != LF);
         parser->nread = 0;
+        nread = 0;
         UPDATE_STATE(s_chunk_size_start);
         CALLBACK_NOTIFY(chunk_complete);
         break;
@@ -2013,7 +2051,7 @@ reexecute:
     }
   }
 
-  /* Run callbacks for any marks that we have leftover after we ran our of
+  /* Run callbacks for any marks that we have leftover after we ran out of
    * bytes. There should be at most one of these set, so it's OK to invoke
    * them in series (unset marks will not result in callbacks).
    *
@@ -2095,6 +2133,16 @@ http_method_str (enum http_method m)
   return ELEM_AT(method_strings, m, "<unknown>");
 }
 
+const char *
+http_status_str (enum http_status s)
+{
+  switch (s) {
+#define XX(num, name, string) case HTTP_STATUS_##name: return #string;
+    HTTP_STATUS_MAP(XX)
+#undef XX
+    default: return "<unknown>";
+  }
+}
 
 void
 http_parser_init (http_parser *parser, enum http_parser_type t)
@@ -2155,7 +2203,7 @@ http_parse_host_char(enum http_host_state s, const char ch) {
         return s_http_host;
       }
 
-    /* FALLTHROUGH */
+    /* fall through */
     case s_http_host_v6_end:
       if (ch == ':') {
         return s_http_host_port_start;
@@ -2168,7 +2216,7 @@ http_parse_host_char(enum http_host_state s, const char ch) {
         return s_http_host_v6_end;
       }
 
-    /* FALLTHROUGH */
+    /* fall through */
     case s_http_host_v6_start:
       if (IS_HEX(ch) || ch == ':' || ch == '.') {
         return s_http_host_v6;
@@ -2184,7 +2232,7 @@ http_parse_host_char(enum http_host_state s, const char ch) {
         return s_http_host_v6_end;
       }
 
-    /* FALLTHROUGH */
+    /* fall through */
     case s_http_host_v6_zone_start:
       /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */
       if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' ||
@@ -2230,14 +2278,14 @@ http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
     switch(new_s) {
       case s_http_host:
         if (s != s_http_host) {
-          u->field_data[UF_HOST].off = p - buf;
+          u->field_data[UF_HOST].off = (uint16_t)(p - buf);
         }
         u->field_data[UF_HOST].len++;
         break;
 
       case s_http_host_v6:
         if (s != s_http_host_v6) {
-          u->field_data[UF_HOST].off = p - buf;
+          u->field_data[UF_HOST].off = (uint16_t)(p - buf);
         }
         u->field_data[UF_HOST].len++;
         break;
@@ -2249,7 +2297,7 @@ http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
 
       case s_http_host_port:
         if (s != s_http_host_port) {
-          u->field_data[UF_PORT].off = p - buf;
+          u->field_data[UF_PORT].off = (uint16_t)(p - buf);
           u->field_data[UF_PORT].len = 0;
           u->field_set |= (1 << UF_PORT);
         }
@@ -2258,7 +2306,7 @@ http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
 
       case s_http_userinfo:
         if (s != s_http_userinfo) {
-          u->field_data[UF_USERINFO].off = p - buf ;
+          u->field_data[UF_USERINFO].off = (uint16_t)(p - buf);
           u->field_data[UF_USERINFO].len = 0;
           u->field_set |= (1 << UF_USERINFO);
         }
@@ -2303,6 +2351,10 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
   enum http_parser_url_fields uf, old_uf;
   int found_at = 0;
 
+  if (buflen == 0) {
+    return 1;
+  }
+
   u->port = u->field_set = 0;
   s = is_connect ? s_req_server_start : s_req_spaces_before_url;
   old_uf = UF_MAX;
@@ -2330,7 +2382,7 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
       case s_req_server_with_at:
         found_at = 1;
 
-      /* FALLTHROUGH */
+      /* fall through */
       case s_req_server:
         uf = UF_HOST;
         break;
@@ -2358,7 +2410,7 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
       continue;
     }
 
-    u->field_data[uf].off = p - buf;
+    u->field_data[uf].off = (uint16_t)(p - buf);
     u->field_data[uf].len = 1;
 
     u->field_set |= (1 << uf);
@@ -2421,6 +2473,7 @@ http_parser_pause(http_parser *parser, int paused) {
    */
   if (HTTP_PARSER_ERRNO(parser) == HPE_OK ||
       HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) {
+    uint32_t nread = parser->nread; /* used by the SET_ERRNO macro */
     SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK);
   } else {
     assert(0 && "Attempting to pause parser in error state");
@@ -2438,3 +2491,8 @@ http_parser_version(void) {
          HTTP_PARSER_VERSION_MINOR * 0x00100 |
          HTTP_PARSER_VERSION_PATCH * 0x00001;
 }
+
+void
+http_parser_set_max_header_size(uint32_t size) {
+  max_header_size = size;
+}

+ 8 - 2
http_parser.h

@@ -26,8 +26,8 @@ extern "C" {
 
 /* Also update SONAME in the Makefile whenever you change these. */
 #define HTTP_PARSER_VERSION_MAJOR 2
-#define HTTP_PARSER_VERSION_MINOR 8
-#define HTTP_PARSER_VERSION_PATCH 1
+#define HTTP_PARSER_VERSION_MINOR 9
+#define HTTP_PARSER_VERSION_PATCH 2
 
 #include <stddef.h>
 #if defined(_WIN32) && !defined(__MINGW32__) && \
@@ -407,6 +407,9 @@ int http_should_keep_alive(const http_parser *parser);
 /* Returns a string version of the HTTP method. */
 const char *http_method_str(enum http_method m);
 
+/* Returns a string version of the HTTP status code. */
+const char *http_status_str(enum http_status s);
+
 /* Return a string name of the given error */
 const char *http_errno_name(enum http_errno err);
 
@@ -427,6 +430,9 @@ void http_parser_pause(http_parser *parser, int paused);
 /* Checks if this is the final chunk of the body. */
 int http_body_is_final(const http_parser *parser);
 
+/* Change the maximum header size provided at compile time. */
+void http_parser_set_max_header_size(uint32_t size);
+
 #ifdef __cplusplus
 }
 #endif

+ 146 - 84
test.c

@@ -43,7 +43,7 @@
 
 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x))
 
-static http_parser *parser;
+static http_parser parser;
 
 struct message {
   const char *name; // for debugging purposes
@@ -153,10 +153,10 @@ const struct message requests[] =
   ,.body= ""
   }
 
-#define DUMBFUCK 2
-, {.name= "dumbfuck"
+#define DUMBLUCK 2
+, {.name= "dumbluck"
   ,.type= HTTP_REQUEST
-  ,.raw= "GET /dumbfuck HTTP/1.1\r\n"
+  ,.raw= "GET /dumbluck HTTP/1.1\r\n"
          "aaaaaaaaaaaaa:++++++++++\r\n"
          "\r\n"
   ,.should_keep_alive= TRUE
@@ -166,8 +166,8 @@ const struct message requests[] =
   ,.method= HTTP_GET
   ,.query_string= ""
   ,.fragment= ""
-  ,.request_path= "/dumbfuck"
-  ,.request_url= "/dumbfuck"
+  ,.request_path= "/dumbluck"
+  ,.request_url= "/dumbluck"
   ,.num_headers= 1
   ,.headers=
     { { "aaaaaaaaaaaaa",  "++++++++++" }
@@ -371,13 +371,13 @@ const struct message requests[] =
   ,.chunk_lengths= { 5, 6 }
   }
 
-#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11
-, {.name= "with bullshit after the length"
+#define CHUNKED_W_NONSENSE_AFTER_LENGTH 11
+, {.name= "with nonsense after the length"
   ,.type= HTTP_REQUEST
-  ,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n"
+  ,.raw= "POST /chunked_w_nonsense_after_length HTTP/1.1\r\n"
          "Transfer-Encoding: chunked\r\n"
          "\r\n"
-         "5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n"
+         "5; ilovew3;whattheluck=aretheseparametersfor\r\nhello\r\n"
          "6; blahblah; blah\r\n world\r\n"
          "0\r\n"
          "\r\n"
@@ -388,8 +388,8 @@ const struct message requests[] =
   ,.method= HTTP_POST
   ,.query_string= ""
   ,.fragment= ""
-  ,.request_path= "/chunked_w_bullshit_after_length"
-  ,.request_url= "/chunked_w_bullshit_after_length"
+  ,.request_path= "/chunked_w_nonsense_after_length"
+  ,.request_url= "/chunked_w_nonsense_after_length"
   ,.num_headers= 1
   ,.headers=
     { { "Transfer-Encoding", "chunked" }
@@ -1173,6 +1173,26 @@ const struct message requests[] =
   ,.headers= { { "Host", "example.com" } }
   ,.body= ""
   }
+
+#define SOURCE_ICE_REQUEST 42
+, {.name = "source request"
+  ,.type= HTTP_REQUEST
+  ,.raw= "SOURCE /music/sweet/music ICE/1.0\r\n"
+         "Host: example.com\r\n"
+         "\r\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 0
+  ,.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 * */
@@ -2010,7 +2030,7 @@ strlncpy(char *dst, size_t len, const char *src, size_t n)
 int
 request_url_cb (http_parser *p, const char *buf, size_t len)
 {
-  assert(p == parser);
+  assert(p == &parser);
   strlncat(messages[num_messages].request_url,
            sizeof(messages[num_messages].request_url),
            buf,
@@ -2021,7 +2041,7 @@ request_url_cb (http_parser *p, const char *buf, size_t len)
 int
 header_field_cb (http_parser *p, const char *buf, size_t len)
 {
-  assert(p == parser);
+  assert(p == &parser);
   struct message *m = &messages[num_messages];
 
   if (m->last_header_element != FIELD)
@@ -2040,7 +2060,7 @@ header_field_cb (http_parser *p, const char *buf, size_t len)
 int
 header_value_cb (http_parser *p, const char *buf, size_t len)
 {
-  assert(p == parser);
+  assert(p == &parser);
   struct message *m = &messages[num_messages];
 
   strlncat(m->headers[m->num_headers-1][1],
@@ -2069,7 +2089,7 @@ check_body_is_final (const http_parser *p)
 int
 body_cb (http_parser *p, const char *buf, size_t len)
 {
-  assert(p == parser);
+  assert(p == &parser);
   strlncat(messages[num_messages].body,
            sizeof(messages[num_messages].body),
            buf,
@@ -2083,7 +2103,7 @@ body_cb (http_parser *p, const char *buf, size_t len)
 int
 count_body_cb (http_parser *p, const char *buf, size_t len)
 {
-  assert(p == parser);
+  assert(p == &parser);
   assert(buf);
   messages[num_messages].body_size += len;
   check_body_is_final(p);
@@ -2093,7 +2113,8 @@ count_body_cb (http_parser *p, const char *buf, size_t len)
 int
 message_begin_cb (http_parser *p)
 {
-  assert(p == parser);
+  assert(p == &parser);
+  assert(!messages[num_messages].message_begin_cb_called);
   messages[num_messages].message_begin_cb_called = TRUE;
   return 0;
 }
@@ -2101,21 +2122,22 @@ message_begin_cb (http_parser *p)
 int
 headers_complete_cb (http_parser *p)
 {
-  assert(p == parser);
-  messages[num_messages].method = parser->method;
-  messages[num_messages].status_code = parser->status_code;
-  messages[num_messages].http_major = parser->http_major;
-  messages[num_messages].http_minor = parser->http_minor;
+  assert(p == &parser);
+  messages[num_messages].method = parser.method;
+  messages[num_messages].status_code = parser.status_code;
+  messages[num_messages].http_major = parser.http_major;
+  messages[num_messages].http_minor = parser.http_minor;
   messages[num_messages].headers_complete_cb_called = TRUE;
-  messages[num_messages].should_keep_alive = http_should_keep_alive(parser);
+  messages[num_messages].should_keep_alive = http_should_keep_alive(&parser);
   return 0;
 }
 
 int
 message_complete_cb (http_parser *p)
 {
-  assert(p == parser);
-  if (messages[num_messages].should_keep_alive != http_should_keep_alive(parser))
+  assert(p == &parser);
+  if (messages[num_messages].should_keep_alive !=
+      http_should_keep_alive(&parser))
   {
     fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same "
                     "value in both on_message_complete and on_headers_complete "
@@ -2146,7 +2168,7 @@ message_complete_cb (http_parser *p)
 int
 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;
 
@@ -2160,7 +2182,7 @@ response_status_cb (http_parser *p, const char *buf, size_t len)
 int
 chunk_header_cb (http_parser *p)
 {
-  assert(p == parser);
+  assert(p == &parser);
   int chunk_idx = messages[num_messages].num_chunks;
   messages[num_messages].num_chunks++;
   if (chunk_idx < MAX_CHUNKS) {
@@ -2173,7 +2195,7 @@ chunk_header_cb (http_parser *p)
 int
 chunk_complete_cb (http_parser *p)
 {
-  assert(p == parser);
+  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
@@ -2378,7 +2400,7 @@ connect_headers_complete_cb (http_parser *p)
 int
 connect_message_complete_cb (http_parser *p)
 {
-  messages[num_messages].should_keep_alive = http_should_keep_alive(parser);
+  messages[num_messages].should_keep_alive = http_should_keep_alive(&parser);
   return message_complete_cb(p);
 }
 
@@ -2451,30 +2473,15 @@ void
 parser_init (enum http_parser_type type)
 {
   num_messages = 0;
-
-  assert(parser == NULL);
-
-  parser = malloc(sizeof(http_parser));
-
-  http_parser_init(parser, type);
-
+  http_parser_init(&parser, type);
   memset(&messages, 0, sizeof messages);
-
-}
-
-void
-parser_free ()
-{
-  assert(parser);
-  free(parser);
-  parser = NULL;
 }
 
 size_t parse (const char *buf, size_t len)
 {
   size_t nparsed;
   currently_parsing_eof = (len == 0);
-  nparsed = http_parser_execute(parser, &settings, buf, len);
+  nparsed = http_parser_execute(&parser, &settings, buf, len);
   return nparsed;
 }
 
@@ -2482,7 +2489,7 @@ size_t parse_count_body (const char *buf, size_t len)
 {
   size_t nparsed;
   currently_parsing_eof = (len == 0);
-  nparsed = http_parser_execute(parser, &settings_count_body, buf, len);
+  nparsed = http_parser_execute(&parser, &settings_count_body, buf, len);
   return nparsed;
 }
 
@@ -2493,7 +2500,7 @@ size_t parse_pause (const char *buf, size_t len)
 
   currently_parsing_eof = (len == 0);
   current_pause_parser = &s;
-  nparsed = http_parser_execute(parser, current_pause_parser, buf, len);
+  nparsed = http_parser_execute(&parser, current_pause_parser, buf, len);
   return nparsed;
 }
 
@@ -2501,7 +2508,7 @@ size_t parse_connect (const char *buf, size_t len)
 {
   size_t nparsed;
   currently_parsing_eof = (len == 0);
-  nparsed = http_parser_execute(parser, &settings_connect, buf, len);
+  nparsed = http_parser_execute(&parser, &settings_connect, buf, len);
   return nparsed;
 }
 
@@ -2721,7 +2728,7 @@ static void
 print_error (const char *raw, size_t error_location)
 {
   fprintf(stderr, "\n*** %s ***\n\n",
-          http_errno_description(HTTP_PARSER_ERRNO(parser)));
+          http_errno_description(HTTP_PARSER_ERRNO(&parser)));
 
   int this_line = 0, char_len = 0;
   size_t i, j, len = strlen(raw), error_location_line = 0;
@@ -3264,6 +3271,24 @@ const struct url_test url_tests[] =
   ,.rv=1 /* s_dead */
   }
 
+, {.name="empty url"
+  ,.url=""
+  ,.is_connect=0
+  ,.rv=1
+  }
+
+, {.name="NULL url"
+  ,.url=NULL
+  ,.is_connect=0
+  ,.rv=1
+  }
+
+, {.name="full of spaces url"
+  ,.url="  "
+  ,.is_connect=0
+  ,.rv=1
+  }
+
 #if HTTP_PARSER_STRICT
 
 , {.name="tab in URL"
@@ -3348,7 +3373,7 @@ test_parse_url (void)
     memset(&u, 0, sizeof(u));
 
     rv = http_parser_parse_url(test->url,
-                               strlen(test->url),
+                               test->url ? strlen(test->url) : 0,
                                test->is_connect,
                                &u);
 
@@ -3389,6 +3414,14 @@ test_method_str (void)
 }
 
 void
+test_status_str (void)
+{
+  assert(0 == strcmp("OK", http_status_str(HTTP_STATUS_OK)));
+  assert(0 == strcmp("Not Found", http_status_str(HTTP_STATUS_NOT_FOUND)));
+  assert(0 == strcmp("<unknown>", http_status_str(1337)));
+}
+
+void
 test_message (const struct message *message)
 {
   size_t raw_len = strlen(message->raw);
@@ -3402,9 +3435,18 @@ test_message (const struct message *message)
     size_t msg2len = raw_len - msg1len;
 
     if (msg1len) {
+      assert(num_messages == 0);
+      messages[0].headers_complete_cb_called = FALSE;
+
       read = parse(msg1, msg1len);
 
-      if (message->upgrade && parser->upgrade && num_messages > 0) {
+      if (!messages[0].headers_complete_cb_called && parser.nread != read) {
+        assert(parser.nread == read);
+        print_error(msg1, read);
+        abort();
+      }
+
+      if (message->upgrade && parser.upgrade && num_messages > 0) {
         messages[num_messages - 1].upgrade = msg1 + read;
         goto test;
       }
@@ -3418,7 +3460,7 @@ test_message (const struct message *message)
 
     read = parse(msg2, msg2len);
 
-    if (message->upgrade && parser->upgrade) {
+    if (message->upgrade && parser.upgrade) {
       messages[num_messages - 1].upgrade = msg2 + read;
       goto test;
     }
@@ -3443,8 +3485,6 @@ test_message (const struct message *message)
     }
 
     if(!message_eq(0, 0, message)) abort();
-
-    parser_free();
   }
 }
 
@@ -3480,8 +3520,6 @@ test_message_count_body (const struct message *message)
   }
 
   if(!message_eq(0, 0, message)) abort();
-
-  parser_free();
 }
 
 void
@@ -3494,11 +3532,9 @@ test_simple_type (const char *buf,
   enum http_errno err;
 
   parse(buf, strlen(buf));
-  err = HTTP_PARSER_ERRNO(parser);
+  err = HTTP_PARSER_ERRNO(&parser);
   parse(NULL, 0);
 
-  parser_free();
-
   /* In strict mode, allow us to pass with an unexpected HPE_STRICT as
    * long as the caller isn't expecting success.
    */
@@ -3838,7 +3874,7 @@ test_multiple3 (const struct message *r1, const struct message *r2, const struct
 
   read = parse(total, strlen(total));
 
-  if (parser->upgrade) {
+  if (parser.upgrade) {
     upgrade_message_fix(total, read, 3, r1, r2, r3);
     goto test;
   }
@@ -3865,8 +3901,6 @@ test:
   if (!message_eq(0, 0, r1)) abort();
   if (message_count > 1 && !message_eq(1, 0, r2)) abort();
   if (message_count > 2 && !message_eq(2, 0, r3)) abort();
-
-  parser_free();
 }
 
 /* SCAN through every possible breaking to make sure the
@@ -3920,9 +3954,17 @@ test_scan (const struct message *r1, const struct message *r2, const struct mess
         strlncpy(buf3, sizeof(buf1), total+j, buf3_len);
         buf3[buf3_len] = 0;
 
+        assert(num_messages == 0);
+        messages[0].headers_complete_cb_called = FALSE;
+
         read = parse(buf1, buf1_len);
 
-        if (parser->upgrade) goto test;
+        if (!messages[0].headers_complete_cb_called && parser.nread != read) {
+          print_error(buf1, read);
+          goto error;
+        }
+
+        if (parser.upgrade) goto test;
 
         if (read != buf1_len) {
           print_error(buf1, read);
@@ -3931,7 +3973,7 @@ test_scan (const struct message *r1, const struct message *r2, const struct mess
 
         read += parse(buf2, buf2_len);
 
-        if (parser->upgrade) goto test;
+        if (parser.upgrade) goto test;
 
         if (read != buf1_len + buf2_len) {
           print_error(buf2, read);
@@ -3940,7 +3982,7 @@ test_scan (const struct message *r1, const struct message *r2, const struct mess
 
         read += parse(buf3, buf3_len);
 
-        if (parser->upgrade) goto test;
+        if (parser.upgrade) goto test;
 
         if (read != buf1_len + buf2_len + buf3_len) {
           print_error(buf3, read);
@@ -3950,7 +3992,7 @@ test_scan (const struct message *r1, const struct message *r2, const struct mess
         parse(NULL, 0);
 
 test:
-        if (parser->upgrade) {
+        if (parser.upgrade) {
           upgrade_message_fix(total, read, 3, r1, r2, r3);
         }
 
@@ -3974,8 +4016,6 @@ test:
           fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n");
           goto error;
         }
-
-        parser_free();
       }
     }
   }
@@ -4039,7 +4079,7 @@ test_message_pause (const struct message *msg)
     // completion callback.
     if (messages[0].message_complete_cb_called &&
         msg->upgrade &&
-        parser->upgrade) {
+        parser.upgrade) {
       messages[0].upgrade = buf + nread;
       goto test;
     }
@@ -4047,17 +4087,16 @@ test_message_pause (const struct message *msg)
     if (nread < buflen) {
 
       // Not much do to if we failed a strict-mode check
-      if (HTTP_PARSER_ERRNO(parser) == HPE_STRICT) {
-        parser_free();
+      if (HTTP_PARSER_ERRNO(&parser) == HPE_STRICT) {
         return;
       }
 
-      assert (HTTP_PARSER_ERRNO(parser) == HPE_PAUSED);
+      assert (HTTP_PARSER_ERRNO(&parser) == HPE_PAUSED);
     }
 
     buf += nread;
     buflen -= nread;
-    http_parser_pause(parser, 0);
+    http_parser_pause(&parser, 0);
   } while (buflen > 0);
 
   nread = parse_pause(NULL, 0);
@@ -4070,8 +4109,6 @@ test:
   }
 
   if(!message_eq(0, 0, msg)) abort();
-
-  parser_free();
 }
 
 /* Verify that body and next message won't be parsed in responses to CONNECT */
@@ -4091,14 +4128,11 @@ test_message_connect (const struct message *msg)
   }
 
   if(!message_eq(0, 1, msg)) abort();
-
-  parser_free();
 }
 
 int
 main (void)
 {
-  parser = NULL;
   unsigned i, j, k;
   unsigned long version;
   unsigned major;
@@ -4117,6 +4151,7 @@ main (void)
   test_preserve_data();
   test_parse_url();
   test_method_str();
+  test_status_str();
 
   //// NREAD
   test_header_nread_value();
@@ -4149,6 +4184,13 @@ main (void)
 
   test_simple_type(
       "POST / HTTP/1.1\r\n"
+      "Content-Length:\r\n"  // empty
+      "\r\n",
+      HPE_INVALID_CONTENT_LENGTH,
+      HTTP_REQUEST);
+
+  test_simple_type(
+      "POST / HTTP/1.1\r\n"
       "Content-Length:  42 \r\n"  // Note the surrounding whitespace.
       "\r\n",
       HPE_OK,
@@ -4168,6 +4210,20 @@ main (void)
       HPE_INVALID_CONTENT_LENGTH,
       HTTP_REQUEST);
 
+  test_simple_type(
+      "POST / HTTP/1.1\r\n"
+      "Content-Length:  42\r\n"
+      " Hello world!\r\n",
+      HPE_INVALID_CONTENT_LENGTH,
+      HTTP_REQUEST);
+
+  test_simple_type(
+      "POST / HTTP/1.1\r\n"
+      "Content-Length:  42\r\n"
+      " \r\n",
+      HPE_OK,
+      HTTP_REQUEST);
+
   //// RESPONSES
 
   test_simple_type("HTP/1.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
@@ -4175,6 +4231,7 @@ main (void)
   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);
+  test_simple_type("\rHTTP/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]);
@@ -4252,11 +4309,16 @@ main (void)
 
   /// REQUESTS
 
+  test_simple("GET / IHTTP/1.0\r\n\r\n", HPE_INVALID_CONSTANT);
+  test_simple("GET / ICE/1.0\r\n\r\n", HPE_INVALID_CONSTANT);
   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);
 
+  test_simple("GET / HTTP/1.0\r\nHello: w\1rld\r\n\r\n", HPE_INVALID_HEADER_TOKEN);
+  test_simple("GET / HTTP/1.0\r\nHello: woooo\2rld\r\n\r\n", HPE_INVALID_HEADER_TOKEN);
+
   // Extended characters - see nodejs/test/parallel/test-http-headers-obstext.js
   test_simple("GET / HTTP/1.1\r\n"
               "Test: Düsseldorf\r\n",
@@ -4339,9 +4401,9 @@ main (void)
               "\r\n",
               HPE_INVALID_HEADER_TOKEN);
 
-  const char *dumbfuck2 =
+  const char *dumbluck2 =
     "GET / HTTP/1.1\r\n"
-    "X-SSL-Bullshit:   -----BEGIN CERTIFICATE-----\r\n"
+    "X-SSL-Nonsense:   -----BEGIN CERTIFICATE-----\r\n"
     "\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n"
     "\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n"
     "\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n"
@@ -4374,7 +4436,7 @@ main (void)
     "\tRA==\r\n"
     "\t-----END CERTIFICATE-----\r\n"
     "\r\n";
-  test_simple(dumbfuck2, HPE_OK);
+  test_simple(dumbluck2, HPE_OK);
 
   const char *corrupted_connection =
     "GET / HTTP/1.1\r\n"
@@ -4441,7 +4503,7 @@ main (void)
   printf("request scan 3/4      ");
   test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END]
            , &requests[CHUNKED_W_TRAILING_HEADERS]
-           , &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH]
+           , &requests[CHUNKED_W_NONSENSE_AFTER_LENGTH]
            );
 
   printf("request scan 4/4      ");