changeset 8958:10522e8dea41 quic

HTTP/3: improved processing of multiple Cookie field lines. As per draft-ietf-quic-http, 4.1.1.2, and similar to HTTP/2 specification, they ought to be concatenated. This closely follows ngx_http_v2_module.
author Sergey Kandaurov <pluknet@nginx.com>
date Thu, 30 Dec 2021 12:59:32 +0300
parents de7d36aa9bc7
children 9aff230f10d1
files src/http/v3/ngx_http_v3.h src/http/v3/ngx_http_v3_request.c
diffstat 2 files changed, 152 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/src/http/v3/ngx_http_v3.h
+++ b/src/http/v3/ngx_http_v3.h
@@ -123,6 +123,7 @@ struct ngx_http_v3_parse_s {
     size_t                        header_limit;
     ngx_http_v3_parse_headers_t   headers;
     ngx_http_v3_parse_data_t      body;
+    ngx_array_t                  *cookies;
 };
 
 
--- a/src/http/v3/ngx_http_v3_request.c
+++ b/src/http/v3/ngx_http_v3_request.c
@@ -25,6 +25,8 @@ static ngx_int_t ngx_http_v3_process_pse
     ngx_str_t *name, ngx_str_t *value);
 static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r);
 static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value);
+static ngx_int_t ngx_http_v3_construct_cookie_header(ngx_http_request_t *r);
 static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r);
 static ngx_int_t ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r);
 static ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r,
@@ -601,6 +603,8 @@ ngx_http_v3_process_header(ngx_http_requ
     ngx_http_core_srv_conf_t   *cscf;
     ngx_http_core_main_conf_t  *cmcf;
 
+    static ngx_str_t cookie = ngx_string("cookie");
+
     len = name->len + value->len;
 
     if (len > r->v3_parse->header_limit) {
@@ -636,24 +640,34 @@ ngx_http_v3_process_header(ngx_http_requ
         return NGX_ERROR;
     }
 
-    h = ngx_list_push(&r->headers_in.headers);
-    if (h == NULL) {
-        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
-        return NGX_ERROR;
-    }
+    if (name->len == cookie.len
+        && ngx_memcmp(name->data, cookie.data, cookie.len) == 0)
+    {
+        if (ngx_http_v3_cookie(r, value) != NGX_OK) {
+            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return NGX_ERROR;
+        }
 
-    h->key = *name;
-    h->value = *value;
-    h->lowcase_key = h->key.data;
-    h->hash = ngx_hash_key(h->key.data, h->key.len);
+    } else {
+        h = ngx_list_push(&r->headers_in.headers);
+        if (h == NULL) {
+            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return NGX_ERROR;
+        }
 
-    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+        h->key = *name;
+        h->value = *value;
+        h->lowcase_key = h->key.data;
+        h->hash = ngx_hash_key(h->key.data, h->key.len);
+
+        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
 
-    hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
-                       h->lowcase_key, h->key.len);
+        hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
+                           h->lowcase_key, h->key.len);
 
-    if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
-        return NGX_ERROR;
+        if (hh && hh->handler(r, h, hh->offset) != NGX_OK) {
+            return NGX_ERROR;
+        }
     }
 
     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
@@ -981,6 +995,10 @@ ngx_http_v3_process_request_header(ngx_h
         return NGX_ERROR;
     }
 
+    if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
     if (r->headers_in.server.len == 0) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0,
                       "client sent neither \":authority\" nor \"Host\" header");
@@ -1056,6 +1074,125 @@ failed:
 }
 
 
+static ngx_int_t
+ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value)
+{
+    ngx_str_t    *val;
+    ngx_array_t  *cookies;
+
+    cookies = r->v3_parse->cookies;
+
+    if (cookies == NULL) {
+        cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t));
+        if (cookies == NULL) {
+            return NGX_ERROR;
+        }
+
+        r->v3_parse->cookies = cookies;
+    }
+
+    val = ngx_array_push(cookies);
+    if (val == NULL) {
+        return NGX_ERROR;
+    }
+
+    *val = *value;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_v3_construct_cookie_header(ngx_http_request_t *r)
+{
+    u_char                     *buf, *p, *end;
+    size_t                      len;
+    ngx_str_t                  *vals;
+    ngx_uint_t                  i;
+    ngx_array_t                *cookies;
+    ngx_table_elt_t            *h;
+    ngx_http_header_t          *hh;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    static ngx_str_t cookie = ngx_string("cookie");
+
+    cookies = r->v3_parse->cookies;
+
+    if (cookies == NULL) {
+        return NGX_OK;
+    }
+
+    vals = cookies->elts;
+
+    i = 0;
+    len = 0;
+
+    do {
+        len += vals[i].len + 2;
+    } while (++i != cookies->nelts);
+
+    len -= 2;
+
+    buf = ngx_pnalloc(r->pool, len + 1);
+    if (buf == NULL) {
+        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_ERROR;
+    }
+
+    p = buf;
+    end = buf + len;
+
+    for (i = 0; /* void */ ; i++) {
+
+        p = ngx_cpymem(p, vals[i].data, vals[i].len);
+
+        if (p == end) {
+            *p = '\0';
+            break;
+        }
+
+        *p++ = ';'; *p++ = ' ';
+    }
+
+    h = ngx_list_push(&r->headers_in.headers);
+    if (h == NULL) {
+        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_ERROR;
+    }
+
+    h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(
+                                    ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e');
+
+    h->key.len = cookie.len;
+    h->key.data = cookie.data;
+
+    h->value.len = len;
+    h->value.data = buf;
+
+    h->lowcase_key = cookie.data;
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash,
+                       h->lowcase_key, h->key.len);
+
+    if (hh == NULL) {
+        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return NGX_ERROR;
+    }
+
+    if (hh->handler(r, h, hh->offset) != NGX_OK) {
+        /*
+         * request has been finalized already
+         * in ngx_http_process_multi_header_lines()
+         */
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
 ngx_int_t
 ngx_http_v3_read_request_body(ngx_http_request_t *r)
 {