Browse Source

Updated QHttp to 5433eb6

Tobias Hieta 9 years ago
parent
commit
63fb8b7ea0

+ 61 - 82
external/qhttp/3rdparty/http-parser/http_parser.c

@@ -123,7 +123,7 @@ do {                                                                 \
     FOR##_mark = NULL;                                               \
   }                                                                  \
 } while (0)
-  
+
 /* Run the data callback FOR and consume the current byte */
 #define CALLBACK_DATA(FOR)                                           \
     CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)
@@ -435,6 +435,12 @@ enum http_host_state
   (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
 #endif
 
+/**
+ * Verify that a char is a valid visible (printable) US-ASCII
+ * character or %x80-FF
+ **/
+#define IS_HEADER_CHAR(ch)                                                     \
+  (ch == CR || ch == LF || ch == 9 || ((unsigned char)ch > 31 && ch != 127))
 
 #define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
 
@@ -639,6 +645,7 @@ size_t http_parser_execute (http_parser *parser,
   const char *body_mark = 0;
   const char *status_mark = 0;
   enum state p_state = (enum state) parser->state;
+  const unsigned int lenient = parser->lenient_http_headers;
 
   /* We're in an error state. Don't bother doing anything. */
   if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
@@ -1000,89 +1007,40 @@ reexecute:
           UPDATE_STATE(s_req_spaces_before_url);
         } else if (ch == matcher[parser->index]) {
           ; /* nada */
-        } else if (parser->method == HTTP_CONNECT) {
-          if (parser->index == 1 && ch == 'H') {
-            parser->method = HTTP_CHECKOUT;
-          } else if (parser->index == 2  && ch == 'P') {
-            parser->method = HTTP_COPY;
-          } else {
-            SET_ERRNO(HPE_INVALID_METHOD);
-            goto error;
-          }
-        } else if (parser->method == HTTP_MKCOL) {
-          if (parser->index == 1 && ch == 'O') {
-            parser->method = HTTP_MOVE;
-          } else if (parser->index == 1 && ch == 'E') {
-            parser->method = HTTP_MERGE;
-          } else if (parser->index == 1 && ch == '-') {
-            parser->method = HTTP_MSEARCH;
-          } else if (parser->index == 2 && ch == 'A') {
-            parser->method = HTTP_MKACTIVITY;
-          } else if (parser->index == 3 && ch == 'A') {
-            parser->method = HTTP_MKCALENDAR;
-          } else {
-            SET_ERRNO(HPE_INVALID_METHOD);
-            goto error;
-          }
-        } else if (parser->method == HTTP_SUBSCRIBE) {
-          if (parser->index == 1 && ch == 'E') {
-            parser->method = HTTP_SEARCH;
-          } else {
-            SET_ERRNO(HPE_INVALID_METHOD);
-            goto error;
-          }
-        } else if (parser->method == HTTP_REPORT) {
-            if (parser->index == 2 && ch == 'B') {
-              parser->method = HTTP_REBIND;
-            } else {
-              SET_ERRNO(HPE_INVALID_METHOD);
-              goto error;
-            }
-        } else if (parser->index == 1) {
-          if (parser->method == HTTP_POST) {
-            if (ch == 'R') {
-              parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */
-            } else if (ch == 'U') {
-              parser->method = HTTP_PUT; /* or HTTP_PURGE */
-            } else if (ch == 'A') {
-              parser->method = HTTP_PATCH;
-            } else {
-              SET_ERRNO(HPE_INVALID_METHOD);
-              goto error;
-            }
-          } else if (parser->method == HTTP_LOCK) {
-            if (ch == 'I') {
-              parser->method = HTTP_LINK;
-            } else {
-              SET_ERRNO(HPE_INVALID_METHOD);
-              goto error;
-            }
-          }
-        } else if (parser->index == 2) {
-          if (parser->method == HTTP_PUT) {
-            if (ch == 'R') {
-              parser->method = HTTP_PURGE;
-            } else {
-              SET_ERRNO(HPE_INVALID_METHOD);
-              goto error;
-            }
-          } else if (parser->method == HTTP_UNLOCK) {
-            if (ch == 'S') {
-              parser->method = HTTP_UNSUBSCRIBE;
-            } else if(ch == 'B') {
-              parser->method = HTTP_UNBIND;
-            } else {
+        } else if (IS_ALPHA(ch)) {
+
+          switch (parser->method << 16 | parser->index << 8 | ch) {
+#define XX(meth, pos, ch, new_meth) \
+            case (HTTP_##meth << 16 | pos << 8 | ch): \
+              parser->method = HTTP_##new_meth; break;
+
+            XX(POST,      1, 'U', PUT)
+            XX(POST,      1, 'A', PATCH)
+            XX(CONNECT,   1, 'H', CHECKOUT)
+            XX(CONNECT,   2, 'P', COPY)
+            XX(MKCOL,     1, 'O', MOVE)
+            XX(MKCOL,     1, 'E', MERGE)
+            XX(MKCOL,     2, 'A', MKACTIVITY)
+            XX(MKCOL,     3, 'A', MKCALENDAR)
+            XX(SUBSCRIBE, 1, 'E', SEARCH)
+            XX(REPORT,    2, 'B', REBIND)
+            XX(POST,      1, 'R', PROPFIND)
+            XX(PROPFIND,  4, 'P', PROPPATCH)
+            XX(PUT,       2, 'R', PURGE)
+            XX(LOCK,      1, 'I', LINK)
+            XX(UNLOCK,    2, 'S', UNSUBSCRIBE)
+            XX(UNLOCK,    2, 'B', UNBIND)
+            XX(UNLOCK,    3, 'I', UNLINK)
+#undef XX
+
+            default:
               SET_ERRNO(HPE_INVALID_METHOD);
               goto error;
-            }
-          } else {
-            SET_ERRNO(HPE_INVALID_METHOD);
-            goto error;
           }
-        } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {
-          parser->method = HTTP_PROPPATCH;
-        } else if (parser->index == 3 && parser->method == HTTP_UNLOCK && ch == 'I') {
-          parser->method = HTTP_UNLINK;
+        } else if (ch == '-' &&
+                   parser->index == 1 &&
+                   parser->method == HTTP_MKCOL) {
+          parser->method = HTTP_MSEARCH;
         } else {
           SET_ERRNO(HPE_INVALID_METHOD);
           goto error;
@@ -1408,7 +1366,12 @@ reexecute:
                   || c != CONTENT_LENGTH[parser->index]) {
                 parser->header_state = h_general;
               } else if (parser->index == sizeof(CONTENT_LENGTH)-2) {
+                if (parser->flags & F_CONTENTLENGTH) {
+                  SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
+                  goto error;
+                }
                 parser->header_state = h_content_length;
+                parser->flags |= F_CONTENTLENGTH;
               }
               break;
 
@@ -1560,6 +1523,11 @@ reexecute:
             REEXECUTE();
           }
 
+          if (!lenient && !IS_HEADER_CHAR(ch)) {
+            SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+            goto error;
+          }
+
           c = LOWER(ch);
 
           switch (h_state) {
@@ -1727,7 +1695,10 @@ reexecute:
 
       case s_header_almost_done:
       {
-        STRICT_CHECK(ch != LF);
+        if (UNLIKELY(ch != LF)) {
+          SET_ERRNO(HPE_LF_EXPECTED);
+          goto error;
+        }
 
         UPDATE_STATE(s_header_value_lws);
         break;
@@ -1811,6 +1782,14 @@ reexecute:
           REEXECUTE();
         }
 
+        /* Cannot use chunked encoding and a content-length header together
+           per the HTTP specification. */
+        if ((parser->flags & F_CHUNKED) &&
+            (parser->flags & F_CONTENTLENGTH)) {
+          SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
+          goto error;
+        }
+
         UPDATE_STATE(s_headers_done);
 
         /* Set this here so that on_headers_complete() callbacks can see it */

+ 8 - 4
external/qhttp/3rdparty/http-parser/http_parser.h

@@ -27,7 +27,7 @@ extern "C" {
 /* Also update SONAME in the Makefile whenever you change these. */
 #define HTTP_PARSER_VERSION_MAJOR 2
 #define HTTP_PARSER_VERSION_MINOR 6
-#define HTTP_PARSER_VERSION_PATCH 0
+#define HTTP_PARSER_VERSION_PATCH 2
 
 #include <sys/types.h>
 #if defined(_WIN32) && !defined(__MINGW32__) && \
@@ -148,6 +148,7 @@ enum flags
   , F_TRAILING              = 1 << 4
   , F_UPGRADE               = 1 << 5
   , F_SKIPBODY              = 1 << 6
+  , F_CONTENTLENGTH         = 1 << 7
   };
 
 
@@ -190,6 +191,8 @@ enum flags
   XX(INVALID_HEADER_TOKEN, "invalid character in header")            \
   XX(INVALID_CONTENT_LENGTH,                                         \
      "invalid character in content-length header")                   \
+  XX(UNEXPECTED_CONTENT_LENGTH,                                      \
+     "unexpected content-length header")                             \
   XX(INVALID_CHUNK_SIZE,                                             \
      "invalid character in chunk size header")                       \
   XX(INVALID_CONSTANT, "invalid constant string")                    \
@@ -214,10 +217,11 @@ enum http_errno {
 struct http_parser {
   /** PRIVATE **/
   unsigned int type : 2;         /* enum http_parser_type */
-  unsigned int flags : 7;        /* F_* values from 'flags' enum; semi-public */
+  unsigned int flags : 8;        /* 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 */
+  unsigned int header_state : 7; /* enum header_state from http_parser.c */
+  unsigned int index : 7;        /* index into current matcher */
+  unsigned int lenient_http_headers : 1;
 
   uint32_t nread;          /* # bytes read in various scenarios */
   uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */

+ 167 - 1
external/qhttp/3rdparty/http-parser/test.c

@@ -2444,7 +2444,7 @@ upgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) {
   va_list ap;
   size_t i;
   size_t off = 0;
- 
+
   va_start(ap, nmsgs);
 
   for (i = 0; i < nmsgs; i++) {
@@ -3270,6 +3270,155 @@ test_simple (const char *buf, enum http_errno err_expected)
   }
 }
 
+void
+test_invalid_header_content (int req, const char* str)
+{
+  http_parser parser;
+  http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+  size_t parsed;
+  const char *buf;
+  buf = req ?
+    "GET / HTTP/1.1\r\n" :
+    "HTTP/1.1 200 OK\r\n";
+  parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
+  assert(parsed == strlen(buf));
+
+  buf = str;
+  size_t buflen = strlen(buf);
+
+  parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
+  if (parsed != buflen) {
+    assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN);
+    return;
+  }
+
+  fprintf(stderr,
+          "\n*** Error expected but none in invalid header content test ***\n");
+  abort();
+}
+
+void
+test_invalid_header_field_content_error (int req)
+{
+  test_invalid_header_content(req, "Foo: F\01ailure");
+  test_invalid_header_content(req, "Foo: B\02ar");
+}
+
+void
+test_invalid_header_field (int req, const char* str)
+{
+  http_parser parser;
+  http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+  size_t parsed;
+  const char *buf;
+  buf = req ?
+    "GET / HTTP/1.1\r\n" :
+    "HTTP/1.1 200 OK\r\n";
+  parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
+  assert(parsed == strlen(buf));
+
+  buf = str;
+  size_t buflen = strlen(buf);
+
+  parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
+  if (parsed != buflen) {
+    assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN);
+    return;
+  }
+
+  fprintf(stderr,
+          "\n*** Error expected but none in invalid header token test ***\n");
+  abort();
+}
+
+void
+test_invalid_header_field_token_error (int req)
+{
+  test_invalid_header_field(req, "Fo@: Failure");
+  test_invalid_header_field(req, "Foo\01\test: Bar");
+}
+
+void
+test_double_content_length_error (int req)
+{
+  http_parser parser;
+  http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+  size_t parsed;
+  const char *buf;
+  buf = req ?
+    "GET / HTTP/1.1\r\n" :
+    "HTTP/1.1 200 OK\r\n";
+  parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
+  assert(parsed == strlen(buf));
+
+  buf = "Content-Length: 0\r\nContent-Length: 1\r\n\r\n";
+  size_t buflen = strlen(buf);
+
+  parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
+  if (parsed != buflen) {
+    assert(HTTP_PARSER_ERRNO(&parser) == HPE_UNEXPECTED_CONTENT_LENGTH);
+    return;
+  }
+
+  fprintf(stderr,
+          "\n*** Error expected but none in double content-length test ***\n");
+  abort();
+}
+
+void
+test_chunked_content_length_error (int req)
+{
+  http_parser parser;
+  http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+  size_t parsed;
+  const char *buf;
+  buf = req ?
+    "GET / HTTP/1.1\r\n" :
+    "HTTP/1.1 200 OK\r\n";
+  parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
+  assert(parsed == strlen(buf));
+
+  buf = "Transfer-Encoding: chunked\r\nContent-Length: 1\r\n\r\n";
+  size_t buflen = strlen(buf);
+
+  parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
+  if (parsed != buflen) {
+    assert(HTTP_PARSER_ERRNO(&parser) == HPE_UNEXPECTED_CONTENT_LENGTH);
+    return;
+  }
+
+  fprintf(stderr,
+          "\n*** Error expected but none in chunked content-length test ***\n");
+  abort();
+}
+
+void
+test_header_cr_no_lf_error (int req)
+{
+  http_parser parser;
+  http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+  size_t parsed;
+  const char *buf;
+  buf = req ?
+    "GET / HTTP/1.1\r\n" :
+    "HTTP/1.1 200 OK\r\n";
+  parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
+  assert(parsed == strlen(buf));
+
+  buf = "Foo: 1\rBar: 1\r\n\r\n";
+  size_t buflen = strlen(buf);
+
+  parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
+  if (parsed != buflen) {
+    assert(HTTP_PARSER_ERRNO(&parser) == HPE_LF_EXPECTED);
+    return;
+  }
+
+  fprintf(stderr,
+          "\n*** Error expected but none in header whitespace test ***\n");
+  abort();
+}
+
 void
 test_header_overflow_error (int req)
 {
@@ -3696,6 +3845,18 @@ main (void)
   test_header_content_length_overflow_error();
   test_chunk_content_length_overflow_error();
 
+  //// HEADER FIELD CONDITIONS
+  test_double_content_length_error(HTTP_REQUEST);
+  test_chunked_content_length_error(HTTP_REQUEST);
+  test_header_cr_no_lf_error(HTTP_REQUEST);
+  test_invalid_header_field_token_error(HTTP_REQUEST);
+  test_invalid_header_field_content_error(HTTP_REQUEST);
+  test_double_content_length_error(HTTP_RESPONSE);
+  test_chunked_content_length_error(HTTP_RESPONSE);
+  test_header_cr_no_lf_error(HTTP_RESPONSE);
+  test_invalid_header_field_token_error(HTTP_RESPONSE);
+  test_invalid_header_field_content_error(HTTP_RESPONSE);
+
   //// RESPONSES
 
   for (i = 0; i < response_count; i++) {
@@ -3772,6 +3933,11 @@ main (void)
 
   test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION);
 
+  // Extended characters - see nodejs/test/parallel/test-http-headers-obstext.js
+  test_simple("GET / HTTP/1.1\r\n"
+              "Test: Düsseldorf\r\n",
+              HPE_OK);
+
   // Well-formed but incomplete
   test_simple("GET / HTTP/1.1\r\n"
               "Content-Type: text/plain\r\n"

+ 5 - 1
external/qhttp/src/private/qhttpclient_private.hpp

@@ -121,6 +121,8 @@ public:
 
 protected:
     void         onConnected() {
+        iconnectingTimer.stop();
+
         if ( itimeOut > 0 )
             itimer.start(itimeOut, Qt::CoarseTimer, q_func());
 
@@ -143,7 +145,7 @@ protected:
 
     void         onDispatchResponse() {
         // if ilastResponse has been sent previously, just return
-        if ( ilastResponse->d_func()->ireadState == QHttpResponsePrivate::ESent )
+        if ( !ilastResponse  ||  ilastResponse->d_func()->ireadState == QHttpResponsePrivate::ESent )
             return;
 
         ilastResponse->d_func()->ireadState = QHttpResponsePrivate::ESent;
@@ -157,6 +159,8 @@ protected:
     QHttpResponse*      ilastResponse = nullptr;
     TRequstHandler      ireqHandler;
     TResponseHandler    irespHandler;
+
+    QBasicTimer         iconnectingTimer;
 };
 
 ///////////////////////////////////////////////////////////////////////////////

+ 1 - 1
external/qhttp/src/private/qhttpserverconnection_private.hpp

@@ -115,7 +115,7 @@ public:
 
     void         onDispatchRequest() {
         // if ilastRequest has been sent previously, just return
-        if ( ilastRequest->d_func()->ireadState == QHttpRequestPrivate::ESent )
+        if ( !ilastRequest || ilastRequest->d_func()->ireadState == QHttpRequestPrivate::ESent )
             return;
 
         ilastRequest->d_func()->ireadState = QHttpRequestPrivate::ESent;

+ 24 - 2
external/qhttp/src/qhttpclient.cpp

@@ -36,7 +36,11 @@ QHttpClient::isOpen() const {
 
 void
 QHttpClient::killConnection() {
-    d_func()->isocket.close();
+    Q_D(QHttpClient);
+
+    d->iconnectingTimer.stop();
+    d->itimer.stop();
+    d->isocket.close();
 }
 
 TBackend
@@ -54,6 +58,21 @@ QHttpClient::localSocket() const {
     return d_func()->isocket.ilocalSocket;
 }
 
+void
+QHttpClient::setConnectingTimeOut(quint32 timeout) {
+    Q_D(QHttpClient);
+
+    if ( timeout == 0 ) {
+        d->iconnectingTimer.stop();
+
+    } else {
+        d->iconnectingTimer.start(timeout,
+                Qt::CoarseTimer,
+                this
+                );
+    }
+}
+
 bool
 QHttpClient::request(THttpMethod method, QUrl url,
                      const TRequstHandler &reqHandler,
@@ -119,7 +138,6 @@ QHttpClient::request(THttpMethod method, QUrl url,
             d->isocket.connectTo(url.host(), url.port(80));
     }
 
-
     return true;
 }
 
@@ -129,6 +147,10 @@ QHttpClient::timerEvent(QTimerEvent *e) {
 
     if ( e->timerId() == d->itimer.timerId() ) {
         killConnection();
+
+    } else if ( e->timerId() == d->iconnectingTimer.timerId() ) {
+        d->iconnectingTimer.stop();
+        emit connectingTimeOut();
     }
 }
 

+ 21 - 3
external/qhttp/src/qhttpclient.hpp

@@ -90,15 +90,30 @@ public:
     void        killConnection();
 
 
-    /** returns time-out value [mSec] for open connections (sockets).
+    /** returns time-out value [mSec] for ESTABLISHED connections (sockets).
      *  @sa setTimeOut(). */
     quint32     timeOut()const;
 
-    /** set time-out for new open connections in miliseconds [mSec].
-     * each connection will be forcefully closed after this timeout.
+    /** set time-out for ESTABLISHED connections in miliseconds [mSec].
+     * each (already opened) connection will be forcefully closed after this timeout.
      *  a zero (0) value disables timer for new connections. */
     void        setTimeOut(quint32);
 
+    /** set a time-out [mSec] for making a new connection (make a request).
+     * if connecting to server takes more than this time-out value,
+     *  the @sa timedOut(quint32) signal will be emitted and connection will be killed.
+     * 0 (default) timeout value means to disable this timer.
+     */
+    void        setConnectingTimeOut(quint32);
+
+    template<class Handler>
+    void        setConnectingTimeOut(quint32 timeout, Handler&& func) {
+        setConnectingTimeOut(timeout);
+        QObject::connect(this, &QHttpClient::connectingTimeOut,
+                std::forward<Handler&&>(func)
+                );
+    }
+
     /** returns the backend type of this client. */
     TBackend    backendType() const;
 
@@ -126,6 +141,9 @@ signals:
     /** emitted when the HTTP connection drops or being disconnected. */
     void        disconnected();
 
+    /// emitted when fails to connect to a HTTP server. @sa setConnectingTimeOut()
+    void        connectingTimeOut();
+
 
 protected:
     /** called when a new HTTP connection is established.