[nginx] HTTP/2: fixed header block parsing with CONTINUATION fra...

Valentin Bartenev vbart at nginx.com
Mon Sep 21 22:41:52 UTC 2015


details:   http://hg.nginx.org/nginx/rev/081a073e5164
branches:  
changeset: 6249:081a073e5164
user:      Valentin Bartenev <vbart at nginx.com>
date:      Tue Sep 22 01:40:04 2015 +0300
description:
HTTP/2: fixed header block parsing with CONTINUATION frames (#792).

It appears that the CONTINUATION frames don't need to be aligned to bounds of
individual headers.

diffstat:

 src/http/v2/ngx_http_v2.c |  211 ++++++++++++++++++++++++++++++---------------
 src/http/v2/ngx_http_v2.h |    1 -
 2 files changed, 139 insertions(+), 73 deletions(-)

diffs (truncated from 320 to 300 lines):

diff -r f5380c244cd7 -r 081a073e5164 src/http/v2/ngx_http_v2.c
--- a/src/http/v2/ngx_http_v2.c	Tue Sep 22 01:40:04 2015 +0300
+++ b/src/http/v2/ngx_http_v2.c	Tue Sep 22 01:40:04 2015 +0300
@@ -86,6 +86,8 @@ static u_char *ngx_http_v2_state_process
     u_char *pos, u_char *end);
 static u_char *ngx_http_v2_state_header_complete(ngx_http_v2_connection_t *h2c,
     u_char *pos, u_char *end);
+static u_char *ngx_http_v2_handle_continuation(ngx_http_v2_connection_t *h2c,
+    u_char *pos, u_char *end, ngx_http_v2_handler_pt handler);
 static u_char *ngx_http_v2_state_priority(ngx_http_v2_connection_t *h2c,
     u_char *pos, u_char *end);
 static u_char *ngx_http_v2_state_rst_stream(ngx_http_v2_connection_t *h2c,
@@ -1198,6 +1200,13 @@ ngx_http_v2_state_header_block(ngx_http_
                                       ngx_http_v2_state_header_block);
     }
 
+    if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG)
+        && h2c->state.length < NGX_HTTP_V2_INT_OCTETS)
+    {
+        return ngx_http_v2_handle_continuation(h2c, pos, end,
+                                               ngx_http_v2_state_header_block);
+    }
+
     size_update = 0;
     indexed = 0;
 
@@ -1295,6 +1304,13 @@ ngx_http_v2_state_field_len(ngx_http_v2_
     ngx_int_t   len;
     ngx_uint_t  huff;
 
+    if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG)
+        && h2c->state.length < NGX_HTTP_V2_INT_OCTETS)
+    {
+        return ngx_http_v2_handle_continuation(h2c, pos, end,
+                                               ngx_http_v2_state_field_len);
+    }
+
     if (h2c->state.length < 1) {
         ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
                       "client sent header block with incorrect length");
@@ -1333,15 +1349,6 @@ ngx_http_v2_state_field_len(ngx_http_v2_
                    "http2 hpack %s string length: %i",
                    huff ? "encoded" : "raw", len);
 
-    if ((size_t) len > h2c->state.length) {
-        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
-                      "client sent header field with incorrect length");
-
-        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
-    }
-
-    h2c->state.length -= len;
-
     if ((size_t) len > h2c->state.field_limit) {
         ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
                       "client exceeded http2_max_field_size limit");
@@ -1385,6 +1392,11 @@ ngx_http_v2_state_field_huff(ngx_http_v2
         size = h2c->state.field_rest;
     }
 
+    if (size > h2c->state.length) {
+        size = h2c->state.length;
+    }
+
+    h2c->state.length -= size;
     h2c->state.field_rest -= size;
 
     if (ngx_http_v2_huff_decode(&h2c->state.field_state, pos, size,
@@ -1399,14 +1411,27 @@ ngx_http_v2_state_field_huff(ngx_http_v2
         return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR);
     }
 
-    if (h2c->state.field_rest != 0) {
-        return ngx_http_v2_state_save(h2c, end, end,
+    pos += size;
+
+    if (h2c->state.field_rest == 0) {
+        *h2c->state.field_end = '\0';
+        return ngx_http_v2_state_process_header(h2c, pos, end);
+    }
+
+    if (h2c->state.length) {
+        return ngx_http_v2_state_save(h2c, pos, end,
                                       ngx_http_v2_state_field_huff);
     }
 
-    *h2c->state.field_end = '\0';
-
-    return ngx_http_v2_state_process_header(h2c, pos + size, end);
+    if (h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent header field with incorrect length");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    return ngx_http_v2_handle_continuation(h2c, pos, end,
+                                           ngx_http_v2_state_field_huff);
 }
 
 
@@ -1422,18 +1447,36 @@ ngx_http_v2_state_field_raw(ngx_http_v2_
         size = h2c->state.field_rest;
     }
 
+    if (size > h2c->state.length) {
+        size = h2c->state.length;
+    }
+
+    h2c->state.length -= size;
     h2c->state.field_rest -= size;
 
     h2c->state.field_end = ngx_cpymem(h2c->state.field_end, pos, size);
 
-    if (h2c->state.field_rest) {
-        return ngx_http_v2_state_save(h2c, end, end,
+    pos += size;
+
+    if (h2c->state.field_rest == 0) {
+        *h2c->state.field_end = '\0';
+        return ngx_http_v2_state_process_header(h2c, pos, end);
+    }
+
+    if (h2c->state.length) {
+        return ngx_http_v2_state_save(h2c, pos, end,
                                       ngx_http_v2_state_field_raw);
     }
 
-    *h2c->state.field_end = '\0';
-
-    return ngx_http_v2_state_process_header(h2c, pos + size, end);
+    if (h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent header field with incorrect length");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    return ngx_http_v2_handle_continuation(h2c, pos, end,
+                                           ngx_http_v2_state_field_raw);
 }
 
 
@@ -1449,14 +1492,33 @@ ngx_http_v2_state_field_skip(ngx_http_v2
         size = h2c->state.field_rest;
     }
 
+    if (size > h2c->state.length) {
+        size = h2c->state.length;
+    }
+
+    h2c->state.length -= size;
     h2c->state.field_rest -= size;
 
-    if (h2c->state.field_rest) {
-        return ngx_http_v2_state_save(h2c, end, end,
+    pos += size;
+
+    if (h2c->state.field_rest == 0) {
+        return ngx_http_v2_state_process_header(h2c, pos, end);
+    }
+
+    if (h2c->state.length) {
+        return ngx_http_v2_state_save(h2c, pos, end,
                                       ngx_http_v2_state_field_skip);
     }
 
-    return ngx_http_v2_state_process_header(h2c, pos + size, end);
+    if (h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                      "client sent header field with incorrect length");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
+    }
+
+    return ngx_http_v2_handle_continuation(h2c, pos, end,
+                                           ngx_http_v2_state_field_skip);
 }
 
 
@@ -1631,16 +1693,15 @@ ngx_http_v2_state_header_complete(ngx_ht
         return pos;
     }
 
+    if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG)) {
+        return ngx_http_v2_handle_continuation(h2c, pos, end,
+                                             ngx_http_v2_state_header_complete);
+    }
+
     stream = h2c->state.stream;
 
     if (stream) {
-        if (h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) {
-            stream->end_headers = 1;
-            ngx_http_v2_run_request(stream->request);
-
-        } else {
-            stream->header_limit = h2c->state.header_limit;
-        }
+        ngx_http_v2_run_request(stream->request);
 
     } else if (h2c->state.pool) {
         ngx_destroy_pool(h2c->state.pool);
@@ -1657,6 +1718,51 @@ ngx_http_v2_state_header_complete(ngx_ht
 
 
 static u_char *
+ngx_http_v2_handle_continuation(ngx_http_v2_connection_t *h2c, u_char *pos,
+    u_char *end, ngx_http_v2_handler_pt handler)
+{
+    u_char    *p;
+    size_t     len;
+    uint32_t   head;
+
+    len = h2c->state.length;
+
+    if ((size_t) (end - pos) < len + NGX_HTTP_V2_FRAME_HEADER_SIZE) {
+        return ngx_http_v2_state_save(h2c, pos, end, handler);
+    }
+
+    p = pos + len;
+
+    head = ngx_http_v2_parse_uint32(p);
+
+    if (ngx_http_v2_parse_type(head) != NGX_HTTP_V2_CONTINUATION_FRAME) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+             "client sent inappropriate frame while CONTINUATION was expected");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+    }
+
+    h2c->state.length += ngx_http_v2_parse_length(head);
+    h2c->state.flags |= p[4];
+
+    if (h2c->state.sid != ngx_http_v2_parse_sid(&p[5])) {
+        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
+                    "client sent CONTINUATION frame with incorrect identifier");
+
+        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
+    }
+
+    p = pos;
+    pos += NGX_HTTP_V2_FRAME_HEADER_SIZE;
+
+    ngx_memcpy(pos, p, len);
+
+    h2c->state.handler = handler;
+    return pos;
+}
+
+
+static u_char *
 ngx_http_v2_state_priority(ngx_http_v2_connection_t *h2c, u_char *pos,
     u_char *end)
 {
@@ -2141,49 +2247,10 @@ static u_char *
 ngx_http_v2_state_continuation(ngx_http_v2_connection_t *h2c, u_char *pos,
     u_char *end)
 {
-    ngx_http_v2_node_t      *node;
-    ngx_http_v2_stream_t    *stream;
-    ngx_http_v2_srv_conf_t  *h2scf;
-
-    if (h2c->state.length == 0) {
-        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
-                      "client sent CONTINUATION with empty header block");
-
-        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR);
-    }
-
-    if (h2c->state.sid == 0) {
-        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
-                   "client sent CONTINUATION frame with incorrect identifier");
-
-        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
-    }
-
-    node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0);
-
-    if (node == NULL || node->stream == NULL) {
-        h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx,
-                                             ngx_http_v2_module);
-
-        h2c->state.header_limit = h2scf->max_header_size;
-
-        return ngx_http_v2_state_skip_headers(h2c, pos, end);
-    }
-
-    stream = node->stream;
-
-    if (stream->end_headers) {
-        ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0,
-                      "client sent unexpected CONTINUATION frame");
-
-        return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR);
-    }
-
-    h2c->state.stream = stream;
-    h2c->state.header_limit = stream->header_limit;
-    h2c->state.pool = stream->request->pool;



More information about the nginx-devel mailing list