From: Sergey Kandaurov Date: Wed, 22 Jun 2022 14:34:58 +0000 (+0400) Subject: Merged with the default branch. X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=c64e2ec1e94974193c286b63db4f58e6e499f5cb;p=nginx.git Merged with the default branch. --- c64e2ec1e94974193c286b63db4f58e6e499f5cb diff --cc README index f1c399b07,000000000..7b7605728 mode 100644,000000..100644 --- a/README +++ b/README @@@ -1,232 -1,0 +1,232 @@@ +Experimental QUIC support for nginx +----------------------------------- + +1. Introduction +2. Installing +3. Configuration +4. Clients +5. Troubleshooting +6. Contributing +7. Links + +1. Introduction + + This is an experimental QUIC [1] / HTTP/3 [2] support for nginx. + + The code is developed in a separate "quic" branch available + at https://hg.nginx.org/nginx-quic. Currently it is based - on nginx mainline 1.21.x. We merge new nginx releases into ++ on nginx mainline 1.23.x. We merge new nginx releases into + this branch regularly. + + The project code base is under the same BSD license as nginx. + + The code is currently at a beta level of quality and should not + be used in production. + + We are working on improving HTTP/3 support with the goal of + integrating it to the main NGINX codebase. Expect frequent + updates of this code and don't rely on it for whatever purpose. + + We'll be grateful for any feedback and code submissions however + we don't bear any responsibilities for any issues with this code. + + You can always contact us via nginx-devel mailing list [3]. + + What works now: + + We support IETF QUIC version 1. Internet drafts are no longer supported. + + nginx should be able to respond to HTTP/3 requests over QUIC and + it should be possible to upload and download big files without errors. + + + The handshake completes successfully + + One endpoint can update keys and its peer responds correctly + + 0-RTT data is being received and acted on + + Connection is established using TLS Resume Ticket + + A handshake that includes a Retry packet completes successfully + + Stream data is being exchanged and ACK'ed + + An H3 transaction succeeded + + One or both endpoints insert entries into dynamic table and + subsequently reference them from header blocks + + Version Negotiation packet is sent to client with unknown version + + Lost packets are detected and retransmitted properly + + Clients may migrate to new address + +2. Installing + + You will need a BoringSSL [4] library that provides QUIC support + + $ hg clone -b quic https://hg.nginx.org/nginx-quic + $ cd nginx-quic + $ ./auto/configure --with-debug --with-http_v3_module \ + --with-cc-opt="-I../boringssl/include" \ + --with-ld-opt="-L../boringssl/build/ssl \ + -L../boringssl/build/crypto" + $ make + + Alternatively, nginx can be configured with QuicTLS [5] + + $ ./auto/configure --with-debug --with-http_v3_module \ + --with-cc-opt="-I../quictls/build/include" \ + --with-ld-opt="-L../quictls/build/lib" + + When configuring nginx, you can enable QUIC and HTTP/3 using the + following new configuration options: + + --with-http_v3_module - enable QUIC and HTTP/3 + --with-stream_quic_module - enable QUIC in Stream + +3. Configuration + + The HTTP "listen" directive got a new option "http3" which enables + HTTP/3 over QUIC on the specified port. + + The Stream "listen" directive got a new option "quic" which enables + QUIC as client transport protocol instead of TCP or plain UDP. + + Along with "http3" or "quic", you also have to specify "reuseport" + option [6] to make it work properly with multiple workers. + + To enable address validation: + + quic_retry on; + + To enable 0-RTT: + + ssl_early_data on; + + Make sure that TLS 1.3 is configured which is required for QUIC: + + ssl_protocols TLSv1.3; + + To enable GSO (Generic Segmentation Offloading): + + quic_gso on; + + To limit maximum UDP payload size on receive path: + + quic_mtu ; + + To set host key for various tokens: + + quic_host_key ; + + + By default, GSO Linux-specific optimization [8] is disabled. + Enable if your network interface is configured to support GSO. + + A number of directives were added that configure HTTP/3: + + http3_stream_buffer_size + http3_max_concurrent_pushes + http3_max_concurrent_streams + http3_push + http3_push_preload + http3_hq (requires NGX_HTTP_V3_HQ macro) + + In http, an additional variable is available: $http3. + The value of $http3 is "h3" for HTTP/3 connections, + "hq" for hq connections, or an empty string otherwise. + + In stream, an additional variable is available: $quic. + The value of $quic is "quic" if QUIC connection is used, + or an empty string otherwise. + +Example configuration: + + http { + log_format quic '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" "$http3"'; + + access_log logs/access.log quic; + + server { + # for better compatibility it's recommended + # to use the same port for quic and https + listen 8443 http3 reuseport; + listen 8443 ssl; + + ssl_certificate certs/example.com.crt; + ssl_certificate_key certs/example.com.key; + ssl_protocols TLSv1.3; + + location / { + # required for browsers to direct them into quic port + add_header Alt-Svc 'h3=":8443"; ma=86400'; + } + } + } + +4. Clients + + * Browsers + + Known to work: Firefox 90+ and Chrome 92+ (QUIC version 1) + + Beware of strange issues: sometimes browser may decide to ignore QUIC + Cache clearing/restart might help. Always check access.log and + error.log to make sure you are using HTTP/3 and not TCP https. + + * Console clients + + Known to work: ngtcp2, firefox's neqo and chromium's console clients: + + $ examples/client 127.0.0.1 8443 https://example.com:8443/index.html + + $ ./neqo-client https://127.0.0.1:8443/ + + $ chromium-build/out/my_build/quic_client http://example.com:8443 + + + If you've got it right, in the access log you should see something like: + + 127.0.0.1 - - [24/Apr/2020:11:27:29 +0300] "GET / HTTP/3" 200 805 "-" + "nghttp3/ngtcp2 client" "quic" + + +5. Troubleshooting + + Here are some tips that may help you to identify problems: + + + Ensure you are building with proper SSL library that supports QUIC + + + Ensure you are using the proper SSL library in runtime + (`nginx -V` will show you what you are using) + + + Ensure your client is actually sending requests over QUIC + (see "Clients" section about browsers and cache) + + We recommend to start with simple console client like ngtcp2 + to ensure you've got server configured properly before trying + with real browsers that may be very picky with certificates, + for example. + + + Build nginx with debug support [7] and check your debug log. + It should contain all details about connection and why it + failed. All related messages contain "quic " prefix and can + be easily filtered out. + + + If you want to investigate deeper, you may want to enable + additional debugging in src/event/quic/ngx_event_quic_connection.h: + + #define NGX_QUIC_DEBUG_PACKETS + #define NGX_QUIC_DEBUG_FRAMES + #define NGX_QUIC_DEBUG_ALLOC + #define NGX_QUIC_DEBUG_CRYPTO + +6. Contributing + + If you are willing to contribute, please refer to + http://nginx.org/en/docs/contributing_changes.html + +7. Links + + [1] https://datatracker.ietf.org/doc/html/rfc9000 + [2] https://datatracker.ietf.org/doc/html/rfc9114 + [3] https://mailman.nginx.org/mailman3/lists/nginx-devel.nginx.org/ + [4] https://boringssl.googlesource.com/boringssl/ + [5] https://github.com/quictls/openssl + [6] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen + [7] https://nginx.org/en/docs/debugging_log.html + [8] http://vger.kernel.org/lpc_net2018_talks/willemdebruijn-lpc2018-udpgso-paper-DRAFT-1.pdf diff --cc src/http/v3/ngx_http_v3_filter_module.c index f835f8e5c,000000000..41b1704bf mode 100644,000000..100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@@ -1,1538 -1,0 +1,1536 @@@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +/* static table indices */ +#define NGX_HTTP_V3_HEADER_AUTHORITY 0 +#define NGX_HTTP_V3_HEADER_PATH_ROOT 1 +#define NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO 4 +#define NGX_HTTP_V3_HEADER_DATE 6 +#define NGX_HTTP_V3_HEADER_LAST_MODIFIED 10 +#define NGX_HTTP_V3_HEADER_LOCATION 12 +#define NGX_HTTP_V3_HEADER_METHOD_GET 17 +#define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22 +#define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23 +#define NGX_HTTP_V3_HEADER_STATUS_200 25 +#define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING 31 +#define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 +#define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59 +#define NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE 72 +#define NGX_HTTP_V3_HEADER_SERVER 92 +#define NGX_HTTP_V3_HEADER_USER_AGENT 95 + + +typedef struct { + ngx_chain_t *free; + ngx_chain_t *busy; +} ngx_http_v3_filter_ctx_t; + + +static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r, + ngx_chain_t ***out); +static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r, + ngx_str_t *path, ngx_chain_t ***out); +static ngx_int_t ngx_http_v3_create_push_request( + ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id); +static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r, + const char *name, ngx_str_t *value); +static void ngx_http_v3_push_request_handler(ngx_event_t *ev); +static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r, + ngx_str_t *path, uint64_t push_id); +static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, + ngx_chain_t *in); +static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r, + ngx_http_v3_filter_ctx_t *ctx); +static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_v3_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_v3_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v3_filter_module = { + NGX_MODULE_V1, + &ngx_http_v3_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; +static ngx_http_output_body_filter_pt ngx_http_next_body_filter; + + +static ngx_int_t +ngx_http_v3_header_filter(ngx_http_request_t *r) +{ + u_char *p; + size_t len, n; + ngx_buf_t *b; + ngx_str_t host, location; + ngx_uint_t i, port; + ngx_chain_t *out, *hl, *cl, **ll; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_filter_ctx_t *ctx; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + u_char addr[NGX_SOCKADDR_STRLEN]; + + if (r->http_version != NGX_HTTP_VERSION_30) { + return ngx_http_next_header_filter(r); + } + + if (r->header_sent) { + return NGX_OK; + } + + r->header_sent = 1; + + if (r != r->main) { + return NGX_OK; + } + + h3c = ngx_http_v3_get_session(r->connection); + + if (r->method == NGX_HTTP_HEAD) { + r->header_only = 1; + } + + if (r->headers_out.last_modified_time != -1) { + if (r->headers_out.status != NGX_HTTP_OK + && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT + && r->headers_out.status != NGX_HTTP_NOT_MODIFIED) + { + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + } + } + + if (r->headers_out.status == NGX_HTTP_NO_CONTENT) { + r->header_only = 1; + ngx_str_null(&r->headers_out.content_type); + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + r->headers_out.content_length = NULL; + r->headers_out.content_length_n = -1; + } + + if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) { + r->header_only = 1; + } + + c = r->connection; + + out = NULL; + ll = &out; + + if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 + && r->method != NGX_HTTP_HEAD) + { + if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) { + return NGX_ERROR; + } + } + + len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + if (r->headers_out.status == NGX_HTTP_OK) { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200); + + } else { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->headers_out.server == NULL) { + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + n = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + n = sizeof(NGINX_VER_BUILD) - 1; + + } else { + n = sizeof("nginx") - 1; + } + + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_SERVER, + NULL, n); + } + + if (r->headers_out.date == NULL) { + len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE, + NULL, ngx_cached_http_time.len); + } + + if (r->headers_out.content_type.len) { + n = r->headers_out.content_type.len; + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + n += sizeof("; charset=") - 1 + r->headers_out.charset.len; + } + + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + NULL, n); + } + + if (r->headers_out.content_length == NULL) { + if (r->headers_out.content_length_n > 0) { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, + NULL, NGX_OFF_T_LEN); + + } else if (r->headers_out.content_length_n == 0) { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); + } + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, + sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); + } + + if (r->headers_out.location && r->headers_out.location->value.len) { + + if (r->headers_out.location->value.data[0] == '/' + && clcf->absolute_redirect) + { + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; + + } else if (r->headers_in.server.len) { + host = r->headers_in.server; + + } else { + host.len = NGX_SOCKADDR_STRLEN; + host.data = addr; + + if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + port = ngx_inet_get_port(c->local_sockaddr); + + location.len = sizeof("https://") - 1 + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + port = (port == 443) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + location.len += sizeof(":65535") - 1; + } + + location.data = ngx_pnalloc(r->pool, location.len); + if (location.data == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(location.data, "https://", sizeof("https://") - 1); + p = ngx_cpymem(p, host.data, host.len); + + if (port) { + p = ngx_sprintf(p, ":%ui", port); + } + + p = ngx_cpymem(p, r->headers_out.location->value.data, + r->headers_out.location->value.len); + + /* update r->headers_out.location->value for possible logging */ + + r->headers_out.location->value.len = p - location.data; + r->headers_out.location->value.data = location.data; + ngx_str_set(&r->headers_out.location->key, "Location"); + } + + r->headers_out.location->hash = 0; + + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LOCATION, NULL, + r->headers_out.location->value.len); + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + if (clcf->gzip_vary) { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); + + } else { + r->gzip_vary = 0; + } + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += ngx_http_v3_encode_field_l(NULL, &header[i].key, + &header[i].value); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, + 0, 0, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \":status: %03ui\"", + r->headers_out.status); + + if (r->headers_out.status == NGX_HTTP_OK) { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200); + + } else { + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); + b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); + } + + if (r->headers_out.server == NULL) { + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + p = (u_char *) NGINX_VER; + n = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + p = (u_char *) NGINX_VER_BUILD; + n = sizeof(NGINX_VER_BUILD) - 1; + + } else { + p = (u_char *) "nginx"; + n = sizeof("nginx") - 1; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"server: %*s\"", n, p); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_SERVER, + p, n); + } + + if (r->headers_out.date == NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"date: %V\"", + &ngx_cached_http_time); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_DATE, + ngx_cached_http_time.data, + ngx_cached_http_time.len); + } + + if (r->headers_out.content_type.len) { + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + n = r->headers_out.content_type.len + sizeof("; charset=") - 1 + + r->headers_out.charset.len; + + p = ngx_pnalloc(r->pool, n); + if (p == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(p, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1); + + p = ngx_cpymem(p, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* updated r->headers_out.content_type is also needed for logging */ + + r->headers_out.content_type.len = n; + r->headers_out.content_type.data = p - n; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"content-type: %V\"", + &r->headers_out.content_type); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + r->headers_out.content_type.data, + r->headers_out.content_type.len); + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"content-length: %O\"", + r->headers_out.content_length_n); + + if (r->headers_out.content_length_n > 0) { + p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); + n = p - b->last; + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, + NULL, n); + + b->last = ngx_sprintf(b->last, "%O", + r->headers_out.content_length_n); + + } else { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); + } + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + n = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1; + + p = ngx_pnalloc(r->pool, n); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_http_time(p, r->headers_out.last_modified_time); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"last-modified: %*s\"", n, p); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, + p, n); + } + + if (r->headers_out.location && r->headers_out.location->value.len) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"location: %V\"", + &r->headers_out.location->value); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LOCATION, + r->headers_out.location->value.data, + r->headers_out.location->value.len); + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"vary: Accept-Encoding\""); + + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"%V: %V\"", + &header[i].key, &header[i].value); + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &header[i].key, + &header[i].value); + } + + if (r->header_only) { + b->last_buf = 1; + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + n = b->last - b->pos; + + h3c->payload_bytes += n; + + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) + + ngx_http_v3_encode_varlen_int(NULL, n); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_HEADERS); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl = ngx_alloc_chain_link(r->pool); + if (hl == NULL) { + return NGX_ERROR; + } + + hl->buf = b; + hl->next = cl; + + *ll = hl; + ll = &cl->next; + + if (r->headers_out.content_length_n >= 0 + && !r->header_only && !r->expect_trailers) + { + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) + + ngx_http_v3_encode_varlen_int(NULL, + r->headers_out.content_length_n); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + r->headers_out.content_length_n); + + h3c->payload_bytes += r->headers_out.content_length_n; + h3c->total_bytes += r->headers_out.content_length_n; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + *ll = cl; + + } else { + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_filter_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_http_set_ctx(r, ctx, ngx_http_v3_filter_module); + } + + for (cl = out; cl; cl = cl->next) { + h3c->total_bytes += cl->buf->last - cl->buf->pos; + } + + return ngx_http_write_filter(r, out); +} + + +static ngx_int_t +ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out) +{ - u_char *start, *end, *last; - ngx_str_t path; - ngx_int_t rc; - ngx_uint_t i, push; - ngx_table_elt_t **h; - ngx_http_v3_loc_conf_t *h3lcf; - ngx_http_complex_value_t *pushes; ++ u_char *start, *end, *last; ++ ngx_str_t path; ++ ngx_int_t rc; ++ ngx_uint_t i, push; ++ ngx_table_elt_t *h; ++ ngx_http_v3_loc_conf_t *h3lcf; ++ ngx_http_complex_value_t *pushes; + + h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module); + + if (h3lcf->pushes) { + pushes = h3lcf->pushes->elts; + + for (i = 0; i < h3lcf->pushes->nelts; i++) { + + if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { + return NGX_ERROR; + } + + if (path.len == 0) { + continue; + } + + if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { + continue; + } + + rc = ngx_http_v3_push_resource(r, &path, out); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_ABORT) { + return NGX_OK; + } + + /* NGX_OK, NGX_DECLINED */ + } + } + + if (!h3lcf->push_preload) { + return NGX_OK; + } + - h = r->headers_out.link.elts; - - for (i = 0; i < r->headers_out.link.nelts; i++) { ++ for (h = r->headers_out.link; h; h = h->next) { + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 parse link: \"%V\"", &h[i]->value); ++ "http3 parse link: \"%V\"", &h->value); + - start = h[i]->value.data; - end = h[i]->value.data + h[i]->value.len; ++ start = h->value.data; ++ end = h->value.data + h->value.len; + + next_link: + + while (start < end && *start == ' ') { start++; } + + if (start == end || *start++ != '<') { + continue; + } + + while (start < end && *start == ' ') { start++; } + + for (last = start; last < end && *last != '>'; last++) { + /* void */ + } + + if (last == start || last == end) { + continue; + } + + path.len = last - start; + path.data = start; + + start = last + 1; + + while (start < end && *start == ' ') { start++; } + + if (start == end) { + continue; + } + + if (*start == ',') { + start++; + goto next_link; + } + + if (*start++ != ';') { + continue; + } + + last = ngx_strlchr(start, end, ','); + + if (last == NULL) { + last = end; + } + + push = 0; + + for ( ;; ) { + + while (start < last && *start == ' ') { start++; } + + if (last - start >= 6 + && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) + { + start += 6; + + if (start == last || *start == ' ' || *start == ';') { + push = 0; + break; + } + + goto next_param; + } + + if (last - start >= 11 + && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) + { + start += 11; + + if (start == last || *start == ' ' || *start == ';') { + push = 1; + } + + goto next_param; + } + + if (last - start >= 4 + && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) + { + start += 4; + + while (start < last && *start == ' ') { start++; } + + if (start == last || *start++ != '"') { + goto next_param; + } + + for ( ;; ) { + + while (start < last && *start == ' ') { start++; } + + if (last - start >= 7 + && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) + { + start += 7; + + if (start < last && (*start == ' ' || *start == '"')) { + push = 1; + break; + } + } + + while (start < last && *start != ' ' && *start != '"') { + start++; + } + + if (start == last) { + break; + } + + if (*start == '"') { + break; + } + + start++; + } + } + + next_param: + + start = ngx_strlchr(start, last, ';'); + + if (start == NULL) { + break; + } + + start++; + } + + if (push) { + while (path.len && path.data[path.len - 1] == ' ') { + path.len--; + } + } + + if (push && path.len + && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) + { + rc = ngx_http_v3_push_resource(r, &path, out); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_ABORT) { + return NGX_OK; + } + + /* NGX_OK, NGX_DECLINED */ + } + + if (last < end) { + start = last + 1; + goto next_link; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, + ngx_chain_t ***ll) +{ + uint64_t push_id; + ngx_int_t rc; + ngx_chain_t *cl; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + + c = r->connection; + h3c = ngx_http_v3_get_session(c); + h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 push \"%V\" pushing:%ui/%ui id:%uL/%L", + path, h3c->npushing, h3scf->max_concurrent_pushes, + h3c->next_push_id, h3c->max_push_id); + + if (!ngx_path_separator(path->data[0])) { + ngx_log_error(NGX_LOG_WARN, c->log, 0, + "non-absolute path \"%V\" not pushed", path); + return NGX_DECLINED; + } + + if (h3c->max_push_id == (uint64_t) -1 + || h3c->next_push_id > h3c->max_push_id) + { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 abort pushes due to max_push_id"); + return NGX_ABORT; + } + + if (h3c->goaway_push_id != (uint64_t) -1) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 abort pushes due to goaway"); + return NGX_ABORT; + } + + if (h3c->npushing >= h3scf->max_concurrent_pushes) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 abort pushes due to max_concurrent_pushes"); + return NGX_ABORT; + } + + if (r->headers_in.server.len == 0) { + return NGX_ABORT; + } + + push_id = h3c->next_push_id++; + + rc = ngx_http_v3_create_push_request(r, path, push_id); + if (rc != NGX_OK) { + return rc; + } + + cl = ngx_http_v3_create_push_promise(r, path, push_id); + if (cl == NULL) { + return NGX_ERROR; + } + + for (**ll = cl; **ll; *ll = &(**ll)->next); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, + uint64_t push_id) +{ + ngx_connection_t *c, *pc; + ngx_http_request_t *r; + ngx_http_log_ctx_t *ctx; + ngx_http_connection_t *hc, *phc; + ngx_http_core_srv_conf_t *cscf; + + pc = pr->connection; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "http3 create push request id:%uL", push_id); + + c = ngx_http_v3_create_push_stream(pc, push_id); + if (c == NULL) { + return NGX_ABORT; + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + + hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t)); + if (hc == NULL) { + ngx_http_close_connection(c); + return NGX_ERROR; + } + + phc = ngx_http_quic_get_connection(pc); + ngx_memcpy(hc, phc, sizeof(ngx_http_connection_t)); + c->data = hc; + + ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + ngx_http_close_connection(c); + return NGX_ERROR; + } + + ctx->connection = c; + ctx->request = NULL; + ctx->current_request = NULL; + + c->log->handler = pc->log->handler; + c->log->data = ctx; + c->log->action = "processing pushed request headers"; + + c->log_error = NGX_ERROR_INFO; + + r = ngx_http_create_request(c); + if (r == NULL) { + ngx_http_close_connection(c); + return NGX_ERROR; + } + + c->data = r; + + ngx_str_set(&r->http_protocol, "HTTP/3.0"); + + r->http_version = NGX_HTTP_VERSION_30; + r->method_name = ngx_http_core_get_method; + r->method = NGX_HTTP_GET; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_in = ngx_create_temp_buf(r->pool, + cscf->client_header_buffer_size); + if (r->header_in == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (ngx_list_init(&r->headers_in.headers, r->pool, 4, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; + + r->schema.data = ngx_pstrdup(r->pool, &pr->schema); + if (r->schema.data == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->schema.len = pr->schema.len; + + r->uri_start = ngx_pstrdup(r->pool, path); + if (r->uri_start == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->uri_end = r->uri_start + path->len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server) + != NGX_OK) + { + return NGX_ERROR; + } + + if (pr->headers_in.accept_encoding) { + if (ngx_http_v3_set_push_header(r, "accept-encoding", + &pr->headers_in.accept_encoding->value) + != NGX_OK) + { + return NGX_ERROR; + } + } + + if (pr->headers_in.accept_language) { + if (ngx_http_v3_set_push_header(r, "accept-language", + &pr->headers_in.accept_language->value) + != NGX_OK) + { + return NGX_ERROR; + } + } + + if (pr->headers_in.user_agent) { + if (ngx_http_v3_set_push_header(r, "user-agent", + &pr->headers_in.user_agent->value) + != NGX_OK) + { + return NGX_ERROR; + } + } + + c->read->handler = ngx_http_v3_push_request_handler; + c->read->handler = ngx_http_v3_push_request_handler; + + ngx_post_event(c->read, &ngx_posted_events); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name, + ngx_str_t *value) +{ + u_char *p; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 push header \"%s\": \"%V\"", name, value); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + p = ngx_pnalloc(r->pool, value->len + 1); + if (p == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + ngx_memcpy(p, value->data, value->len); + p[value->len] = '\0'; + + 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->key.data = (u_char *) name; + h->key.len = ngx_strlen(name); + h->hash = ngx_hash_key(h->key.data, h->key.len); + h->lowcase_key = (u_char *) name; + h->value.data = p; + h->value.len = value->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; + } + + return NGX_OK; +} + + +static void +ngx_http_v3_push_request_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_http_request_t *r; + + c = ev->data; + r = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler"); + + ngx_http_process_request(r); +} + + +static ngx_chain_t * +ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, + uint64_t push_id) +{ + size_t n, len; + ngx_buf_t *b; + ngx_chain_t *hl, *cl; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(r->connection); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 create push promise id:%uL", push_id); + + len = ngx_http_v3_encode_varlen_int(NULL, push_id); + + len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_METHOD_GET); + + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_AUTHORITY, + NULL, r->headers_in.server.len); + + if (path->len == 1 && path->data[0] == '/') { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT); + + } else { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT, + NULL, path->len); + } + + if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTPS); + + } else if (r->schema.len == 4 + && ngx_strncmp(r->schema.data, "http", 4) == 0) + { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP); + + } else { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP, + NULL, r->schema.len); + } + + if (r->headers_in.accept_encoding) { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL, + r->headers_in.accept_encoding->value.len); + } + + if (r->headers_in.accept_language) { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL, + r->headers_in.accept_language->value.len); + } + + if (r->headers_in.user_agent) { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_USER_AGENT, NULL, + r->headers_in.user_agent->value.len); + } + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id); + + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, + 0, 0, 0); + + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_METHOD_GET); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_AUTHORITY, + r->headers_in.server.data, + r->headers_in.server.len); + + if (path->len == 1 && path->data[0] == '/') { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT); + + } else { + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT, + path->data, path->len); + } + + if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTPS); + + } else if (r->schema.len == 4 + && ngx_strncmp(r->schema.data, "http", 4) == 0) + { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP); + + } else { + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP, + r->schema.data, r->schema.len); + } + + if (r->headers_in.accept_encoding) { + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, + r->headers_in.accept_encoding->value.data, + r->headers_in.accept_encoding->value.len); + } + + if (r->headers_in.accept_language) { + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, + r->headers_in.accept_language->value.data, + r->headers_in.accept_language->value.len); + } + + if (r->headers_in.user_agent) { + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_USER_AGENT, + r->headers_in.user_agent->value.data, + r->headers_in.user_agent->value.len); + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = b; + cl->next = NULL; + + n = b->last - b->pos; + + h3c->payload_bytes += n; + + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE) + + ngx_http_v3_encode_varlen_int(NULL, n); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_PUSH_PROMISE); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl = ngx_alloc_chain_link(r->pool); + if (hl == NULL) { + return NULL; + } + + hl->buf = b; + hl->next = cl; + + return hl; +} + + +static ngx_int_t +ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + u_char *chunk; + off_t size; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t *out, *cl, *tl, **ll; + ngx_http_v3_session_t *h3c; + ngx_http_v3_filter_ctx_t *ctx; + + if (in == NULL) { + return ngx_http_next_body_filter(r, in); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_v3_filter_module); + if (ctx == NULL) { + return ngx_http_next_body_filter(r, in); + } + + h3c = ngx_http_v3_get_session(r->connection); + + out = NULL; + ll = &out; + + size = 0; + cl = in; + + for ( ;; ) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 chunk: %O", ngx_buf_size(cl->buf)); + + size += ngx_buf_size(cl->buf); + + if (cl->buf->flush + || cl->buf->sync + || ngx_buf_in_memory(cl->buf) + || cl->buf->in_file) + { + tl = ngx_alloc_chain_link(r->pool); + if (tl == NULL) { + return NGX_ERROR; + } + + tl->buf = cl->buf; + *ll = tl; + ll = &tl->next; + } + + if (cl->next == NULL) { + break; + } + + cl = cl->next; + } + + if (size) { + tl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (tl == NULL) { + return NGX_ERROR; + } + + b = tl->buf; + chunk = b->start; + + if (chunk == NULL) { + chunk = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2); + if (chunk == NULL) { + return NGX_ERROR; + } + + b->start = chunk; + b->end = chunk + NGX_HTTP_V3_VARLEN_INT_LEN * 2; + } + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; + b->temporary = 1; + b->pos = chunk; + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(chunk, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size); + + tl->next = out; + out = tl; + + h3c->payload_bytes += size; + } + + if (cl->buf->last_buf) { + tl = ngx_http_v3_create_trailers(r, ctx); + if (tl == NULL) { + return NGX_ERROR; + } + + cl->buf->last_buf = 0; + + *ll = tl; + + } else { + *ll = NULL; + } + + for (cl = out; cl; cl = cl->next) { + h3c->total_bytes += cl->buf->last - cl->buf->pos; + } + + rc = ngx_http_next_body_filter(r, out); + + ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, + (ngx_buf_tag_t) &ngx_http_v3_filter_module); + + return rc; +} + + +static ngx_chain_t * +ngx_http_v3_create_trailers(ngx_http_request_t *r, + ngx_http_v3_filter_ctx_t *ctx) +{ + size_t len, n; + u_char *p; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t *cl, *hl; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(r->connection); + + len = 0; + + part = &r->headers_out.trailers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += ngx_http_v3_encode_field_l(NULL, &header[i].key, + &header[i].value); + } + + cl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (cl == NULL) { + return NULL; + } + + b = cl->buf; + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; + b->last_buf = 1; + + if (len == 0) { + b->temporary = 0; + b->pos = b->last = NULL; + return cl; + } + + b->temporary = 1; + + len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + b->pos = ngx_palloc(r->pool, len); + if (b->pos == NULL) { + return NULL; + } + + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->pos, + 0, 0, 0); + + part = &r->headers_out.trailers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 output trailer: \"%V: %V\"", + &header[i].key, &header[i].value); + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &header[i].key, + &header[i].value); + } + + n = b->last - b->pos; + + h3c->payload_bytes += n; + + hl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (hl == NULL) { + return NULL; + } + + b = hl->buf; + p = b->start; + + if (p == NULL) { + p = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2); + if (p == NULL) { + return NULL; + } + + b->start = p; + b->end = p + NGX_HTTP_V3_VARLEN_INT_LEN * 2; + } + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; + b->temporary = 1; + b->pos = p; + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(p, + NGX_HTTP_V3_FRAME_HEADERS); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl->next = cl; + + return hl; +} + + +static ngx_int_t +ngx_http_v3_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_v3_header_filter; + + ngx_http_next_body_filter = ngx_http_top_body_filter; + ngx_http_top_body_filter = ngx_http_v3_body_filter; + + return NGX_OK; +}