Merge branch 'nginx' (nginx-1.13.10). Change-Id: I4574318f47a0506c4dfebb19d775e8f76ec136fe Signed-off-by: Piotr Sikora <piotrsikora@google.com>
diff --git a/.hgtags b/.hgtags index fce3720..dfe844f 100644 --- a/.hgtags +++ b/.hgtags
@@ -422,3 +422,4 @@ 47cca243d0ed39bf5dcb9859184affc958b79b6f release-1.13.7 20ca4bcff108d3e66977f4d97508637093492287 release-1.13.8 fb1212c7eca4c5328fe17d6cd95b010c67336aac release-1.13.9 +31c929e16910c38492581ef474e72fa67c28f124 release-1.13.10
diff --git a/BUILD b/BUILD index d9ca7b1..01e88fc 100644 --- a/BUILD +++ b/BUILD
@@ -412,6 +412,7 @@ "src/http/ngx_http_write_filter_module.c", "src/http/v2/ngx_http_v2.c", "src/http/v2/ngx_http_v2.h", + "src/http/v2/ngx_http_v2_encode.c", "src/http/v2/ngx_http_v2_filter_module.c", "src/http/v2/ngx_http_v2_huff_decode.c", "src/http/v2/ngx_http_v2_huff_encode.c", @@ -625,6 +626,21 @@ ) cc_library( + name = "http_grpc", + srcs = [ + "src/http/modules/ngx_http_grpc_module.c", + ], + copts = nginx_copts, + defines = [ + "NGX_HTTP_GRPC", + ], + deps = [ + ":core", + ":http", + ], +) + +cc_library( name = "http_gunzip", srcs = [ "src/http/modules/ngx_http_gunzip_filter_module.c", @@ -1365,6 +1381,7 @@ ":http_fastcgi", ":http_flv", ":http_geo", + ":http_grpc", ":http_gunzip", ":http_gzip_filter", ":http_gzip_static", @@ -1485,5 +1502,5 @@ preinst = "@nginx_pkgoss//:debian_preinst", prerm = "@nginx_pkgoss//:debian_prerm", section = "httpd", - version = "1.13.9", + version = "1.13.10", )
diff --git a/auto/modules b/auto/modules index a8e55d5..8e14758 100644 --- a/auto/modules +++ b/auto/modules
@@ -428,6 +428,7 @@ src/http/v2/ngx_http_v2_module.h" ngx_module_srcs="src/http/v2/ngx_http_v2.c \ src/http/v2/ngx_http_v2_table.c \ + src/http/v2/ngx_http_v2_encode.c \ src/http/v2/ngx_http_v2_huff_decode.c \ src/http/v2/ngx_http_v2_huff_encode.c \ src/http/v2/ngx_http_v2_module.c" @@ -743,6 +744,17 @@ . $NGX_AUTO/module fi + if [ $HTTP_GRPC = YES -a $HTTP_V2 = YES ]; then + ngx_module_name=ngx_http_grpc_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs=src/http/modules/ngx_http_grpc_module.c + ngx_module_libs= + ngx_module_link=$HTTP_GRPC + + . $NGX_AUTO/module + fi + if [ $HTTP_PERL != NO ]; then ngx_module_name=ngx_http_perl_module ngx_module_incs=src/http/modules/perl
diff --git a/auto/options b/auto/options index 4c9314f..8b02f54 100644 --- a/auto/options +++ b/auto/options
@@ -86,6 +86,7 @@ HTTP_FASTCGI=YES HTTP_UWSGI=YES HTTP_SCGI=YES +HTTP_GRPC=YES HTTP_PERL=NO HTTP_MEMCACHED=YES HTTP_LIMIT_CONN=YES @@ -262,6 +263,7 @@ --without-http_fastcgi_module) HTTP_FASTCGI=NO ;; --without-http_uwsgi_module) HTTP_UWSGI=NO ;; --without-http_scgi_module) HTTP_SCGI=NO ;; + --without-http_grpc_module) HTTP_GRPC=NO ;; --without-http_memcached_module) HTTP_MEMCACHED=NO ;; --without-http_limit_conn_module) HTTP_LIMIT_CONN=NO ;; --without-http_limit_req_module) HTTP_LIMIT_REQ=NO ;; @@ -471,6 +473,7 @@ --without-http_fastcgi_module disable ngx_http_fastcgi_module --without-http_uwsgi_module disable ngx_http_uwsgi_module --without-http_scgi_module disable ngx_http_scgi_module + --without-http_grpc_module disable ngx_http_grpc_module --without-http_memcached_module disable ngx_http_memcached_module --without-http_limit_conn_module disable ngx_http_limit_conn_module --without-http_limit_req_module disable ngx_http_limit_req_module
diff --git a/auto/unix b/auto/unix index be43017..b600b5e 100644 --- a/auto/unix +++ b/auto/unix
@@ -791,6 +791,30 @@ . $NGX_AUTO/feature +ngx_feature="clock_gettime(CLOCK_MONOTONIC)" +ngx_feature_name="NGX_HAVE_CLOCK_MONOTONIC" +ngx_feature_run=no +ngx_feature_incs="#include <time.h>" +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts)" +. $NGX_AUTO/feature + + +if [ $ngx_found = no ]; then + + # Linux before glibc 2.17, notably CentOS 6 + + ngx_feature="clock_gettime(CLOCK_MONOTONIC) in librt" + ngx_feature_libs="-lrt" + . $NGX_AUTO/feature + + if [ $ngx_found = yes ]; then + CORE_LIBS="$CORE_LIBS -lrt" + fi +fi + + ngx_feature="posix_memalign()" ngx_feature_name="NGX_HAVE_POSIX_MEMALIGN" ngx_feature_run=no
diff --git a/build.bzl b/build.bzl index 9548c2b..28f57a2 100644 --- a/build.bzl +++ b/build.bzl
@@ -663,7 +663,7 @@ name = "nginx_pkgoss", build_file_content = _PKGOSS_BUILD_FILE.format(nginx = nginx) + _PKGOSS_BUILD_FILE_TAIL, - commit = "908c0e6cd38e1abdaa0af8c2c8969a96b5a67fde", # nginx-1.13.9 + commit = "7a50651b4ca9dacc720b1c8f17d2dc31f12c9ff7", # nginx-1.13.10 remote = "https://nginx.googlesource.com/nginx-pkgoss", )
diff --git a/contrib/vim/syntax/nginx.vim b/contrib/vim/syntax/nginx.vim index 0a21372..075b19a 100644 --- a/contrib/vim/syntax/nginx.vim +++ b/contrib/vim/syntax/nginx.vim
@@ -241,6 +241,32 @@ syn keyword ngxDirective contained geoip_proxy syn keyword ngxDirective contained geoip_proxy_recursive syn keyword ngxDirective contained google_perftools_profiles +syn keyword ngxDirective contained grpc_bind +syn keyword ngxDirective contained grpc_buffer_size +syn keyword ngxDirective contained grpc_connect_timeout +syn keyword ngxDirective contained grpc_hide_header +syn keyword ngxDirective contained grpc_ignore_headers +syn keyword ngxDirective contained grpc_intercept_errors +syn keyword ngxDirective contained grpc_next_upstream +syn keyword ngxDirective contained grpc_next_upstream_timeout +syn keyword ngxDirective contained grpc_next_upstream_tries +syn keyword ngxDirective contained grpc_pass +syn keyword ngxDirective contained grpc_pass_header +syn keyword ngxDirective contained grpc_read_timeout +syn keyword ngxDirective contained grpc_send_timeout +syn keyword ngxDirective contained grpc_set_header +syn keyword ngxDirective contained grpc_ssl_certificate +syn keyword ngxDirective contained grpc_ssl_certificate_key +syn keyword ngxDirective contained grpc_ssl_ciphers +syn keyword ngxDirective contained grpc_ssl_crl +syn keyword ngxDirective contained grpc_ssl_name +syn keyword ngxDirective contained grpc_ssl_password_file +syn keyword ngxDirective contained grpc_ssl_protocols +syn keyword ngxDirective contained grpc_ssl_server_name +syn keyword ngxDirective contained grpc_ssl_session_reuse +syn keyword ngxDirective contained grpc_ssl_trusted_certificate +syn keyword ngxDirective contained grpc_ssl_verify +syn keyword ngxDirective contained grpc_ssl_verify_depth syn keyword ngxDirective contained gunzip syn keyword ngxDirective contained gunzip_buffers syn keyword ngxDirective contained gzip @@ -268,11 +294,14 @@ syn keyword ngxDirective contained http2_body_preread_size syn keyword ngxDirective contained http2_chunk_size syn keyword ngxDirective contained http2_idle_timeout +syn keyword ngxDirective contained http2_max_concurrent_pushes syn keyword ngxDirective contained http2_max_concurrent_streams syn keyword ngxDirective contained http2_max_field_size syn keyword ngxDirective contained http2_max_header_size syn keyword ngxDirective contained http2_max_requests syn keyword ngxDirective contained http2_pool_size +syn keyword ngxDirective contained http2_push +syn keyword ngxDirective contained http2_push_preload syn keyword ngxDirective contained http2_recv_buffer_size syn keyword ngxDirective contained http2_recv_timeout syn keyword ngxDirective contained http2_streams_index_size @@ -574,6 +603,7 @@ syn keyword ngxDirective contained sub_filter_last_modified syn keyword ngxDirective contained sub_filter_once syn keyword ngxDirective contained sub_filter_types +syn keyword ngxDirective contained subrequest_output_buffer_size syn keyword ngxDirective contained tcp_nodelay syn keyword ngxDirective contained tcp_nopush syn keyword ngxDirective contained thread_pool @@ -1323,11 +1353,8 @@ " Phusion Passenger " https://www.phusionpassenger.com/library/config/nginx/reference/ -syn keyword ngxDirectiveThirdParty contained disable_security_update_check syn keyword ngxDirectiveThirdParty contained passenger_abort_on_startup_error syn keyword ngxDirectiveThirdParty contained passenger_abort_websockets_on_process_shutdown -syn keyword ngxDirectiveThirdParty contained passenger_analytics_log_group -syn keyword ngxDirectiveThirdParty contained passenger_analytics_log_user syn keyword ngxDirectiveThirdParty contained passenger_app_env syn keyword ngxDirectiveThirdParty contained passenger_app_file_descriptor_ulimit syn keyword ngxDirectiveThirdParty contained passenger_app_group_name @@ -1369,6 +1396,7 @@ syn keyword ngxDirectiveThirdParty contained passenger_max_pool_size syn keyword ngxDirectiveThirdParty contained passenger_max_preloader_idle_time syn keyword ngxDirectiveThirdParty contained passenger_max_request_queue_size +syn keyword ngxDirectiveThirdParty contained passenger_max_request_queue_time syn keyword ngxDirectiveThirdParty contained passenger_max_request_time syn keyword ngxDirectiveThirdParty contained passenger_max_requests syn keyword ngxDirectiveThirdParty contained passenger_memory_limit @@ -1402,21 +1430,22 @@ syn keyword ngxDirectiveThirdParty contained passenger_user syn keyword ngxDirectiveThirdParty contained passenger_user_switching syn keyword ngxDirectiveThirdParty contained passenger_vary_turbocache_by_cookie -syn keyword ngxDirectiveThirdParty contained rack_env -syn keyword ngxDirectiveThirdParty contained rails_app_spawner_idle_time -syn keyword ngxDirectiveThirdParty contained rails_env -syn keyword ngxDirectiveThirdParty contained security_update_check_proxy -syn keyword ngxDirectiveThirdParty contained union_station_filter -syn keyword ngxDirectiveThirdParty contained union_station_gateway_address -syn keyword ngxDirectiveThirdParty contained union_station_gateway_cert -syn keyword ngxDirectiveThirdParty contained union_station_gateway_port -syn keyword ngxDirectiveThirdParty contained union_station_key -syn keyword ngxDirectiveThirdParty contained union_station_proxy_address -syn keyword ngxDirectiveThirdParty contained union_station_support +syn keyword ngxDirectiveThirdPartyDeprecated contained passenger_analytics_log_group +syn keyword ngxDirectiveThirdPartyDeprecated contained passenger_analytics_log_user syn keyword ngxDirectiveThirdPartyDeprecated contained passenger_debug_log_file syn keyword ngxDirectiveThirdPartyDeprecated contained passenger_use_global_queue +syn keyword ngxDirectiveThirdPartyDeprecated contained rack_env +syn keyword ngxDirectiveThirdPartyDeprecated contained rails_app_spawner_idle_time +syn keyword ngxDirectiveThirdPartyDeprecated contained rails_env syn keyword ngxDirectiveThirdPartyDeprecated contained rails_framework_spawner_idle_time syn keyword ngxDirectiveThirdPartyDeprecated contained rails_spawn_method +syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_filter +syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_gateway_address +syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_gateway_cert +syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_gateway_port +syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_key +syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_proxy_address +syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_support " ngx_postgres is an upstream module that allows nginx to communicate directly with PostgreSQL database " https://github.com/FRiCKLE/ngx_postgres @@ -2028,6 +2057,7 @@ syn keyword ngxDirectiveThirdParty contained selective_cache_purge_query syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_database syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_host +syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_password syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_port syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_unix_socket
diff --git a/docs/xml/nginx/changes.xml b/docs/xml/nginx/changes.xml index a58e164..7cb1fcf 100644 --- a/docs/xml/nginx/changes.xml +++ b/docs/xml/nginx/changes.xml
@@ -5,6 +5,90 @@ <change_log title="nginx"> +<changes ver="1.13.10" date="2018-03-20"> + +<change type="feature"> +<para lang="ru"> +теперь параметр set в SSI-директиве include +позволяет сохранять в переменную любые ответы; +максимальный размер ответа задаётся директивой subrequest_output_buffer_size. +</para> +<para lang="en"> +the "set" parameter of the "include" SSI directive now allows +writing arbitrary responses to a variable; +the "subrequest_output_buffer_size" directive defines maximum response size. +</para> +</change> + +<change type="feature"> +<para lang="ru"> +теперь nginx использует вызов clock_gettime(CLOCK_MONOTONIC), если он доступен, +что позволяет избежать некорректного срабатывания таймаутов +при изменениях системного времени. +</para> +<para lang="en"> +now nginx uses clock_gettime(CLOCK_MONOTONIC) if available, +to avoid timeouts being incorrectly triggered +on system time changes. +</para> +</change> + +<change type="feature"> +<para lang="ru"> +параметр "escape=none" директивы log_format.<br/> +Спасибо Johannes Baiter и Calin Don. +</para> +<para lang="en"> +the "escape=none" parameter of the "log_format" directive.<br/> +Thanks to Johannes Baiter and Calin Don. +</para> +</change> + +<change type="feature"> +<para lang="ru"> +переменная $ssl_preread_alpn_protocols +в модуле ngx_stream_ssl_preread_module. +</para> +<para lang="en"> +the $ssl_preread_alpn_protocols variable +in the ngx_stream_ssl_preread_module. +</para> +</change> + +<change type="feature"> +<para lang="ru"> +модуль ngx_http_grpc_module. +</para> +<para lang="en"> +the ngx_http_grpc_module. +</para> +</change> + +<change type="bugfix"> +<para lang="ru"> +в обработке ошибок выделения памяти в директиве geo. +</para> +<para lang="en"> +in memory allocation error handling in the "geo" directive. +</para> +</change> + +<change type="bugfix"> +<para lang="ru"> +при использовании переменных в директиве auth_basic_user_file +в лог мог выводиться символ '\0'.<br/> +Спасибо Вадиму Филимонову. +</para> +<para lang="en"> +when using variables in the "auth_basic_user_file" directive +a null character might appear in logs.<br/> +Thanks to Vadim Filimonov. +</para> +</change> + +</changes> + + <changes ver="1.13.9" date="2018-02-20"> <change type="feature">
diff --git a/src/core/nginx.h b/src/core/nginx.h index df8fd80..b6a45db 100644 --- a/src/core/nginx.h +++ b/src/core/nginx.h
@@ -13,8 +13,8 @@ #define NGINX_NAME "nginx" #endif -#define nginx_version 1013009 -#define NGINX_VERSION "1.13.9" +#define nginx_version 1013010 +#define NGINX_VERSION "1.13.10" #define NGINX_VER NGINX_NAME "/" NGINX_VERSION #ifdef NGX_BUILD
diff --git a/src/core/ngx_times.c b/src/core/ngx_times.c index d8818ab..ff81b09 100644 --- a/src/core/ngx_times.c +++ b/src/core/ngx_times.c
@@ -9,6 +9,9 @@ #include <ngx_core.h> +static ngx_msec_t ngx_monotonic_time(time_t sec, ngx_uint_t msec); + + /* * The time may be updated by signal handler or by several threads. * The time update operations are rare and require to hold the ngx_time_lock. @@ -93,7 +96,7 @@ sec = tv.tv_sec; msec = tv.tv_usec / 1000; - ngx_current_msec = (ngx_msec_t) sec * 1000 + msec; + ngx_current_msec = ngx_monotonic_time(sec, msec); tp = &cached_time[slot]; @@ -189,6 +192,31 @@ } +static ngx_msec_t +ngx_monotonic_time(time_t sec, ngx_uint_t msec) +{ +#if (NGX_HAVE_CLOCK_MONOTONIC) + struct timespec ts; + +#if defined(CLOCK_MONOTONIC_FAST) + clock_gettime(CLOCK_MONOTONIC_FAST, &ts); + +#elif defined(CLOCK_MONOTONIC_COARSE) + clock_gettime(CLOCK_MONOTONIC_COARSE, &ts); + +#else + clock_gettime(CLOCK_MONOTONIC, &ts); +#endif + + sec = ts.tv_sec; + msec = ts.tv_nsec / 1000000; + +#endif + + return (ngx_msec_t) sec * 1000 + msec; +} + + #if !(NGX_WIN32) void
diff --git a/src/event/ngx_event_connect.c b/src/event/ngx_event_connect.c index c5bb806..e7f28c9 100644 --- a/src/event/ngx_event_connect.c +++ b/src/event/ngx_event_connect.c
@@ -388,7 +388,16 @@ return NGX_ERROR; } +#else + + ngx_log_error(NGX_LOG_ALERT, pc->log, 0, + "could not enable transparent proxying for IPv6 " + "on this platform"); + + return NGX_ERROR; + #endif + break; #endif /* NGX_HAVE_INET6 */
diff --git a/src/http/modules/ngx_http_auth_basic_module.c b/src/http/modules/ngx_http_auth_basic_module.c index 2f345b6..a6f9ec4 100644 --- a/src/http/modules/ngx_http_auth_basic_module.c +++ b/src/http/modules/ngx_http_auth_basic_module.c
@@ -266,8 +266,8 @@ } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "user \"%V\" was not found in \"%V\"", - &r->headers_in.user, &user_file); + "user \"%V\" was not found in \"%s\"", + &r->headers_in.user, user_file.data); return ngx_http_auth_basic_set_realm(r, &realm); }
diff --git a/src/http/modules/ngx_http_fastcgi_module.c b/src/http/modules/ngx_http_fastcgi_module.c index 7ee515f..bc43f53 100644 --- a/src/http/modules/ngx_http_fastcgi_module.c +++ b/src/http/modules/ngx_http_fastcgi_module.c
@@ -2512,36 +2512,6 @@ break; } - /* provide continuous buffer for subrequests in memory */ - - if (r->subrequest_in_memory) { - - cl = u->out_bufs; - - if (cl) { - buf->pos = cl->buf->pos; - } - - buf->last = buf->pos; - - for (cl = u->out_bufs; cl; cl = cl->next) { - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http fastcgi in memory %p-%p %O", - cl->buf->pos, cl->buf->last, ngx_buf_size(cl->buf)); - - if (buf->last == cl->buf->pos) { - buf->last = cl->buf->last; - continue; - } - - buf->last = ngx_movemem(buf->last, cl->buf->pos, - cl->buf->last - cl->buf->pos); - - cl->buf->pos = buf->last - (cl->buf->last - cl->buf->pos); - cl->buf->last = buf->last; - } - } - return NGX_OK; } @@ -2736,8 +2706,6 @@ * conf->upstream.cache_methods = 0; * conf->upstream.temp_path = NULL; * conf->upstream.hide_headers_hash = { NULL, 0 }; - * conf->upstream.uri = { 0, NULL }; - * conf->upstream.location = NULL; * conf->upstream.store_lengths = NULL; * conf->upstream.store_values = NULL; * @@ -2789,10 +2757,10 @@ conf->upstream.intercept_errors = NGX_CONF_UNSET; - /* the hardcoded values */ + /* "fastcgi_cyclic_temp_file" is disabled */ conf->upstream.cyclic_temp_file = 0; + conf->upstream.change_buffering = 1; - conf->upstream.pass_trailers = 0; conf->catch_stderr = NGX_CONF_UNSET_PTR;
diff --git a/src/http/modules/ngx_http_geo_module.c b/src/http/modules/ngx_http_geo_module.c index 8262c9d..c11bafa 100644 --- a/src/http/modules/ngx_http_geo_module.c +++ b/src/http/modules/ngx_http_geo_module.c
@@ -439,6 +439,7 @@ ctx.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); if (ctx.temp_pool == NULL) { + ngx_destroy_pool(pool); return NGX_CONF_ERROR; } @@ -460,6 +461,10 @@ *cf = save; + if (rv != NGX_CONF_OK) { + goto failed; + } + geo->proxies = ctx.proxies; geo->proxy_recursive = ctx.proxy_recursive; @@ -482,7 +487,7 @@ ctx.high.low[i] = ngx_palloc(cf->pool, len + sizeof(void *)); if (ctx.high.low[i] == NULL) { - return NGX_CONF_ERROR; + goto failed; } ngx_memcpy(ctx.high.low[i], a->elts, len); @@ -508,14 +513,11 @@ var->get_handler = ngx_http_geo_range_variable; var->data = (uintptr_t) geo; - ngx_destroy_pool(ctx.temp_pool); - ngx_destroy_pool(pool); - } else { if (ctx.tree == NULL) { ctx.tree = ngx_radix_tree_create(cf->pool, -1); if (ctx.tree == NULL) { - return NGX_CONF_ERROR; + goto failed; } } @@ -525,7 +527,7 @@ if (ctx.tree6 == NULL) { ctx.tree6 = ngx_radix_tree_create(cf->pool, -1); if (ctx.tree6 == NULL) { - return NGX_CONF_ERROR; + goto failed; } } @@ -535,14 +537,11 @@ var->get_handler = ngx_http_geo_cidr_variable; var->data = (uintptr_t) geo; - ngx_destroy_pool(ctx.temp_pool); - ngx_destroy_pool(pool); - if (ngx_radix32tree_insert(ctx.tree, 0, 0, (uintptr_t) &ngx_http_variable_null_value) == NGX_ERROR) { - return NGX_CONF_ERROR; + goto failed; } /* NGX_BUSY is okay (default was set explicitly) */ @@ -552,12 +551,22 @@ (uintptr_t) &ngx_http_variable_null_value) == NGX_ERROR) { - return NGX_CONF_ERROR; + goto failed; } #endif } - return rv; + ngx_destroy_pool(ctx.temp_pool); + ngx_destroy_pool(pool); + + return NGX_CONF_OK; + +failed: + + ngx_destroy_pool(ctx.temp_pool); + ngx_destroy_pool(pool); + + return NGX_CONF_ERROR; }
diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c new file mode 100644 index 0000000..b6be9b8 --- /dev/null +++ b/src/http/modules/ngx_http_grpc_module.c
@@ -0,0 +1,4640 @@ + +/* + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +typedef struct { + ngx_array_t *flushes; + ngx_array_t *lengths; + ngx_array_t *values; + ngx_hash_t hash; +} ngx_http_grpc_headers_t; + + +typedef struct { + ngx_http_upstream_conf_t upstream; + + ngx_http_grpc_headers_t headers; + ngx_array_t *headers_source; + + ngx_str_t host; + ngx_uint_t host_set; + +#if (NGX_HTTP_SSL) + ngx_uint_t ssl; + ngx_uint_t ssl_protocols; + ngx_str_t ssl_ciphers; + ngx_uint_t ssl_verify_depth; + ngx_str_t ssl_trusted_certificate; + ngx_str_t ssl_crl; + ngx_str_t ssl_certificate; + ngx_str_t ssl_certificate_key; + ngx_array_t *ssl_passwords; +#endif +} ngx_http_grpc_loc_conf_t; + + +typedef enum { + ngx_http_grpc_st_start = 0, + ngx_http_grpc_st_length_2, + ngx_http_grpc_st_length_3, + ngx_http_grpc_st_type, + ngx_http_grpc_st_flags, + ngx_http_grpc_st_stream_id, + ngx_http_grpc_st_stream_id_2, + ngx_http_grpc_st_stream_id_3, + ngx_http_grpc_st_stream_id_4, + ngx_http_grpc_st_payload, + ngx_http_grpc_st_padding +} ngx_http_grpc_state_e; + + +typedef struct { + size_t init_window; + size_t send_window; + size_t recv_window; + ngx_uint_t last_stream_id; +} ngx_http_grpc_conn_t; + + +typedef struct { + ngx_http_grpc_state_e state; + ngx_uint_t frame_state; + ngx_uint_t fragment_state; + + ngx_chain_t *in; + ngx_chain_t *out; + ngx_chain_t *free; + ngx_chain_t *busy; + + ngx_http_grpc_conn_t *connection; + + ngx_uint_t id; + + ssize_t send_window; + size_t recv_window; + + size_t rest; + ngx_uint_t stream_id; + u_char type; + u_char flags; + u_char padding; + + ngx_uint_t error; + ngx_uint_t window_update; + + ngx_uint_t setting_id; + ngx_uint_t setting_value; + + u_char ping_data[8]; + + ngx_uint_t index; + ngx_str_t name; + ngx_str_t value; + + u_char *field_end; + size_t field_length; + size_t field_rest; + u_char field_state; + + unsigned literal:1; + unsigned field_huffman:1; + + unsigned header_sent:1; + unsigned output_closed:1; + unsigned parsing_headers:1; + unsigned end_stream:1; + unsigned status:1; + + ngx_http_request_t *request; +} ngx_http_grpc_ctx_t; + + +typedef struct { + u_char length_0; + u_char length_1; + u_char length_2; + u_char type; + u_char flags; + u_char stream_id_0; + u_char stream_id_1; + u_char stream_id_2; + u_char stream_id_3; +} ngx_http_grpc_frame_t; + + +static ngx_int_t ngx_http_grpc_create_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_grpc_reinit_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_grpc_body_output_filter(void *data, ngx_chain_t *in); +static ngx_int_t ngx_http_grpc_process_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_grpc_filter_init(void *data); +static ngx_int_t ngx_http_grpc_filter(void *data, ssize_t bytes); + +static ngx_int_t ngx_http_grpc_parse_frame(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_grpc_parse_header(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_grpc_parse_fragment(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_grpc_validate_header_name(ngx_http_request_t *r, + ngx_str_t *s); +static ngx_int_t ngx_http_grpc_validate_header_value(ngx_http_request_t *r, + ngx_str_t *s); +static ngx_int_t ngx_http_grpc_parse_rst_stream(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_grpc_parse_goaway(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_grpc_parse_window_update(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_grpc_parse_settings(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_grpc_parse_ping(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b); + +static ngx_int_t ngx_http_grpc_send_settings_ack(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx); +static ngx_int_t ngx_http_grpc_send_ping_ack(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx); +static ngx_int_t ngx_http_grpc_send_window_update(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx); + +static ngx_chain_t *ngx_http_grpc_get_buf(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx); +static ngx_http_grpc_ctx_t *ngx_http_grpc_get_ctx(ngx_http_request_t *r); +static ngx_int_t ngx_http_grpc_get_connection_data(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx, ngx_peer_connection_t *pc); +static void ngx_http_grpc_cleanup(void *data); + +static void ngx_http_grpc_abort_request(ngx_http_request_t *r); +static void ngx_http_grpc_finalize_request(ngx_http_request_t *r, + ngx_int_t rc); + +static ngx_int_t ngx_http_grpc_internal_trailers_variable( + ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); + +static ngx_int_t ngx_http_grpc_add_variables(ngx_conf_t *cf); +static void *ngx_http_grpc_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, + void *parent, void *child); +static ngx_int_t ngx_http_grpc_init_headers(ngx_conf_t *cf, + ngx_http_grpc_loc_conf_t *conf, ngx_http_grpc_headers_t *headers, + ngx_keyval_t *default_headers); + +static char *ngx_http_grpc_pass(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +#if (NGX_HTTP_SSL) +static char *ngx_http_grpc_ssl_password_file(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); +static ngx_int_t ngx_http_grpc_set_ssl(ngx_conf_t *cf, + ngx_http_grpc_loc_conf_t *glcf); +#endif + + +static ngx_conf_bitmask_t ngx_http_grpc_next_upstream_masks[] = { + { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR }, + { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT }, + { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER }, + { ngx_string("non_idempotent"), NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT }, + { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 }, + { ngx_string("http_502"), NGX_HTTP_UPSTREAM_FT_HTTP_502 }, + { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 }, + { ngx_string("http_504"), NGX_HTTP_UPSTREAM_FT_HTTP_504 }, + { ngx_string("http_403"), NGX_HTTP_UPSTREAM_FT_HTTP_403 }, + { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 }, + { ngx_string("http_429"), NGX_HTTP_UPSTREAM_FT_HTTP_429 }, + { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF }, + { ngx_null_string, 0 } +}; + + +#if (NGX_HTTP_SSL) + +static ngx_conf_bitmask_t ngx_http_grpc_ssl_protocols[] = { + { ngx_string("SSLv2"), NGX_SSL_SSLv2 }, + { ngx_string("SSLv3"), NGX_SSL_SSLv3 }, + { ngx_string("TLSv1"), NGX_SSL_TLSv1 }, + { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 }, + { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 }, + { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 }, + { ngx_null_string, 0 } +}; + +#endif + + +static ngx_command_t ngx_http_grpc_commands[] = { + + { ngx_string("grpc_pass"), + NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, + ngx_http_grpc_pass, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("grpc_bind"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, + ngx_http_upstream_bind_set_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.local), + NULL }, + + { ngx_string("grpc_connect_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.connect_timeout), + NULL }, + + { ngx_string("grpc_send_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.send_timeout), + NULL }, + + { ngx_string("grpc_intercept_errors"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.intercept_errors), + NULL }, + + { ngx_string("grpc_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.buffer_size), + NULL }, + + { ngx_string("grpc_read_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.read_timeout), + NULL }, + + { ngx_string("grpc_next_upstream"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_conf_set_bitmask_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.next_upstream), + &ngx_http_grpc_next_upstream_masks }, + + { ngx_string("grpc_next_upstream_tries"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.next_upstream_tries), + NULL }, + + { ngx_string("grpc_next_upstream_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.next_upstream_timeout), + NULL }, + + { ngx_string("grpc_set_header"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, + ngx_conf_set_keyval_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, headers_source), + NULL }, + + { ngx_string("grpc_pass_header"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_array_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.pass_headers), + NULL }, + + { ngx_string("grpc_hide_header"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_array_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.hide_headers), + NULL }, + + { ngx_string("grpc_ignore_headers"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_conf_set_bitmask_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.ignore_headers), + &ngx_http_upstream_ignore_headers_masks }, + +#if (NGX_HTTP_SSL) + + { ngx_string("grpc_ssl_session_reuse"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_session_reuse), + NULL }, + + { ngx_string("grpc_ssl_protocols"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_conf_set_bitmask_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, ssl_protocols), + &ngx_http_grpc_ssl_protocols }, + + { ngx_string("grpc_ssl_ciphers"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, ssl_ciphers), + NULL }, + + { ngx_string("grpc_ssl_name"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_http_set_complex_value_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_name), + NULL }, + + { ngx_string("grpc_ssl_server_name"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_server_name), + NULL }, + + { ngx_string("grpc_ssl_verify"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_verify), + NULL }, + + { ngx_string("grpc_ssl_verify_depth"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, ssl_verify_depth), + NULL }, + + { ngx_string("grpc_ssl_trusted_certificate"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, ssl_trusted_certificate), + NULL }, + + { ngx_string("grpc_ssl_crl"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, ssl_crl), + NULL }, + + { ngx_string("grpc_ssl_certificate"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, ssl_certificate), + NULL }, + + { ngx_string("grpc_ssl_certificate_key"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_grpc_loc_conf_t, ssl_certificate_key), + NULL }, + + { ngx_string("grpc_ssl_password_file"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_http_grpc_ssl_password_file, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + +#endif + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_grpc_module_ctx = { + ngx_http_grpc_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_http_grpc_create_loc_conf, /* create location configuration */ + ngx_http_grpc_merge_loc_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_grpc_module = { + NGX_MODULE_V1, + &ngx_http_grpc_module_ctx, /* module context */ + ngx_http_grpc_commands, /* 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 u_char ngx_http_grpc_connection_start[] = + "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" /* connection preface */ + + "\x00\x00\x12\x04\x00\x00\x00\x00\x00" /* settings frame */ + "\x00\x01\x00\x00\x00\x00" /* header table size */ + "\x00\x02\x00\x00\x00\x00" /* disable push */ + "\x00\x04\x7f\xff\xff\xff" /* initial window */ + + "\x00\x00\x04\x08\x00\x00\x00\x00\x00" /* window update frame */ + "\x7f\xff\x00\x00"; + + +static ngx_keyval_t ngx_http_grpc_headers[] = { + { ngx_string("Content-Length"), ngx_string("$content_length") }, + { ngx_string("TE"), ngx_string("$grpc_internal_trailers") }, + { ngx_string("Host"), ngx_string("") }, + { ngx_string("Connection"), ngx_string("") }, + { ngx_string("Transfer-Encoding"), ngx_string("") }, + { ngx_string("Keep-Alive"), ngx_string("") }, + { ngx_string("Expect"), ngx_string("") }, + { ngx_string("Upgrade"), ngx_string("") }, + { ngx_null_string, ngx_null_string } +}; + + +static ngx_str_t ngx_http_grpc_hide_headers[] = { + ngx_string("Date"), + ngx_string("Server"), + ngx_string("X-Accel-Expires"), + ngx_string("X-Accel-Redirect"), + ngx_string("X-Accel-Limit-Rate"), + ngx_string("X-Accel-Buffering"), + ngx_string("X-Accel-Charset"), + ngx_null_string +}; + + +static ngx_http_variable_t ngx_http_grpc_vars[] = { + + { ngx_string("grpc_internal_trailers"), NULL, + ngx_http_grpc_internal_trailers_variable, 0, + NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 }, + + ngx_http_null_variable +}; + + +static ngx_int_t +ngx_http_grpc_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_http_upstream_t *u; + ngx_http_grpc_ctx_t *ctx; + ngx_http_grpc_loc_conf_t *glcf; + + if (ngx_http_upstream_create(r) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + glcf = ngx_http_get_module_loc_conf(r, ngx_http_grpc_module); + + u = r->upstream; + +#if (NGX_HTTP_SSL) + u->ssl = (glcf->upstream.ssl != NULL); + + if (u->ssl) { + ngx_str_set(&u->schema, "grpcs://"); + + } else { + ngx_str_set(&u->schema, "grpc://"); + } +#else + ngx_str_set(&u->schema, "grpc://"); +#endif + + u->output.tag = (ngx_buf_tag_t) &ngx_http_grpc_module; + + u->conf = &glcf->upstream; + + u->create_request = ngx_http_grpc_create_request; + u->reinit_request = ngx_http_grpc_reinit_request; + u->process_header = ngx_http_grpc_process_header; + u->abort_request = ngx_http_grpc_abort_request; + u->finalize_request = ngx_http_grpc_finalize_request; + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_grpc_ctx_t)); + if (ctx == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ctx->request = r; + + ngx_http_set_ctx(r, ctx, ngx_http_grpc_module); + + u->input_filter_init = ngx_http_grpc_filter_init; + u->input_filter = ngx_http_grpc_filter; + u->input_filter_ctx = ctx; + + r->request_body_no_buffering = 1; + + rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_grpc_create_request(ngx_http_request_t *r) +{ + u_char *p, *tmp, *key_tmp, *val_tmp, *headers_frame; + size_t len, tmp_len, key_len, val_len, uri_len; + uintptr_t escape; + ngx_buf_t *b; + ngx_uint_t i, next; + ngx_chain_t *cl, *body; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_http_upstream_t *u; + ngx_http_grpc_frame_t *f; + ngx_http_script_code_pt code; + ngx_http_grpc_loc_conf_t *glcf; + ngx_http_script_engine_t e, le; + ngx_http_script_len_code_pt lcode; + + u = r->upstream; + + glcf = ngx_http_get_module_loc_conf(r, ngx_http_grpc_module); + + len = sizeof(ngx_http_grpc_connection_start) - 1 + + sizeof(ngx_http_grpc_frame_t); /* headers frame */ + + /* :method header */ + + if (r->method == NGX_HTTP_GET || r->method == NGX_HTTP_POST) { + len += 1; + tmp_len = 0; + + } else { + len += 1 + NGX_HTTP_V2_INT_OCTETS + r->method_name.len; + tmp_len = r->method_name.len; + } + + /* :scheme header */ + + len += 1; + + /* :path header */ + + if (r->valid_unparsed_uri) { + escape = 0; + uri_len = r->unparsed_uri.len; + + } else { + escape = 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, + NGX_ESCAPE_URI); + uri_len = r->uri.len + escape + sizeof("?") - 1 + r->args.len; + } + + len += 1 + NGX_HTTP_V2_INT_OCTETS + uri_len; + + if (tmp_len < uri_len) { + tmp_len = uri_len; + } + + /* :authority header */ + + if (!glcf->host_set) { + len += 1 + NGX_HTTP_V2_INT_OCTETS + glcf->host.len; + + if (tmp_len < glcf->host.len) { + tmp_len = glcf->host.len; + } + } + + /* other headers */ + + ngx_http_script_flush_no_cacheable_variables(r, glcf->headers.flushes); + ngx_memzero(&le, sizeof(ngx_http_script_engine_t)); + + le.ip = glcf->headers.lengths->elts; + le.request = r; + le.flushed = 1; + + while (*(uintptr_t *) le.ip) { + + lcode = *(ngx_http_script_len_code_pt *) le.ip; + key_len = lcode(&le); + + for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { + lcode = *(ngx_http_script_len_code_pt *) le.ip; + } + le.ip += sizeof(uintptr_t); + + if (val_len == 0) { + continue; + } + + len += 1 + NGX_HTTP_V2_INT_OCTETS + key_len + + NGX_HTTP_V2_INT_OCTETS + val_len; + + if (tmp_len < key_len) { + tmp_len = key_len; + } + + if (tmp_len < val_len) { + tmp_len = val_len; + } + } + + if (glcf->upstream.pass_request_headers) { + part = &r->headers_in.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 (ngx_hash_find(&glcf->headers.hash, header[i].hash, + header[i].lowcase_key, header[i].key.len)) + { + continue; + } + + len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len + + NGX_HTTP_V2_INT_OCTETS + header[i].value.len; + + if (tmp_len < header[i].key.len) { + tmp_len = header[i].key.len; + } + + if (tmp_len < header[i].value.len) { + tmp_len = header[i].value.len; + } + } + } + + /* continuation frames */ + + len += sizeof(ngx_http_grpc_frame_t) + * (len / NGX_HTTP_V2_DEFAULT_FRAME_SIZE); + + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + tmp = ngx_palloc(r->pool, tmp_len * 3); + if (tmp == NULL) { + return NGX_ERROR; + } + + key_tmp = tmp + tmp_len; + val_tmp = tmp + 2 * tmp_len; + + /* connection preface */ + + b->last = ngx_copy(b->last, ngx_http_grpc_connection_start, + sizeof(ngx_http_grpc_connection_start) - 1); + + /* headers frame */ + + headers_frame = b->last; + + f = (ngx_http_grpc_frame_t *) b->last; + b->last += sizeof(ngx_http_grpc_frame_t); + + f->length_0 = 0; + f->length_1 = 0; + f->length_2 = 0; + f->type = NGX_HTTP_V2_HEADERS_FRAME; + f->flags = 0; + f->stream_id_0 = 0; + f->stream_id_1 = 0; + f->stream_id_2 = 0; + f->stream_id_3 = 1; + + if (r->method == NGX_HTTP_GET) { + *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_METHOD_GET_INDEX); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: \":method: GET\""); + + } else if (r->method == NGX_HTTP_POST) { + *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_METHOD_POST_INDEX); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: \":method: POST\""); + + } else { + *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_METHOD_INDEX); + b->last = ngx_http_v2_write_value(b->last, r->method_name.data, + r->method_name.len, tmp); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: \":method: %V\"", &r->method_name); + } + +#if (NGX_HTTP_SSL) + if (glcf->ssl) { + *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTPS_INDEX); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: \":scheme: https\""); + } else +#endif + { + *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: \":scheme: http\""); + } + + if (r->valid_unparsed_uri) { + + if (r->unparsed_uri.len == 1 && r->unparsed_uri.data[0] == '/') { + *b->last++ = ngx_http_v2_indexed(NGX_HTTP_V2_PATH_ROOT_INDEX); + + } else { + *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); + b->last = ngx_http_v2_write_value(b->last, r->unparsed_uri.data, + r->unparsed_uri.len, tmp); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: \":path: %V\"", &r->unparsed_uri); + + } else if (escape || r->args.len > 0) { + p = val_tmp; + + if (escape) { + p = (u_char *) ngx_escape_uri(p, r->uri.data, r->uri.len, + NGX_ESCAPE_URI); + + } else { + p = ngx_copy(p, r->uri.data, r->uri.len); + } + + if (r->args.len > 0) { + *p++ = '?'; + p = ngx_copy(p, r->args.data, r->args.len); + } + + *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); + b->last = ngx_http_v2_write_value(b->last, val_tmp, p - val_tmp, tmp); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: \":path: %*s\"", p - val_tmp, val_tmp); + + } else { + *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); + b->last = ngx_http_v2_write_value(b->last, r->uri.data, + r->uri.len, tmp); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: \":path: %V\"", &r->uri); + } + + if (!glcf->host_set) { + *b->last++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_AUTHORITY_INDEX); + b->last = ngx_http_v2_write_value(b->last, glcf->host.data, + glcf->host.len, tmp); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: \":authority: %V\"", &glcf->host); + } + + ngx_memzero(&e, sizeof(ngx_http_script_engine_t)); + + e.ip = glcf->headers.values->elts; + e.request = r; + e.flushed = 1; + + le.ip = glcf->headers.lengths->elts; + + while (*(uintptr_t *) le.ip) { + + lcode = *(ngx_http_script_len_code_pt *) le.ip; + key_len = lcode(&le); + + for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { + lcode = *(ngx_http_script_len_code_pt *) le.ip; + } + le.ip += sizeof(uintptr_t); + + if (val_len == 0) { + e.skip = 1; + + while (*(uintptr_t *) e.ip) { + code = *(ngx_http_script_code_pt *) e.ip; + code((ngx_http_script_engine_t *) &e); + } + e.ip += sizeof(uintptr_t); + + e.skip = 0; + + continue; + } + + *b->last++ = 0; + + e.pos = key_tmp; + + code = *(ngx_http_script_code_pt *) e.ip; + code((ngx_http_script_engine_t *) &e); + + b->last = ngx_http_v2_write_name(b->last, key_tmp, key_len, tmp); + + e.pos = val_tmp; + + while (*(uintptr_t *) e.ip) { + code = *(ngx_http_script_code_pt *) e.ip; + code((ngx_http_script_engine_t *) &e); + } + e.ip += sizeof(uintptr_t); + + b->last = ngx_http_v2_write_value(b->last, val_tmp, val_len, tmp); + +#if (NGX_DEBUG) + if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) { + ngx_strlow(key_tmp, key_tmp, key_len); + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: \"%*s: %*s\"", + key_len, key_tmp, val_len, val_tmp); + } +#endif + } + + if (glcf->upstream.pass_request_headers) { + part = &r->headers_in.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 (ngx_hash_find(&glcf->headers.hash, header[i].hash, + header[i].lowcase_key, header[i].key.len)) + { + continue; + } + + *b->last++ = 0; + + b->last = ngx_http_v2_write_name(b->last, header[i].key.data, + header[i].key.len, tmp); + + b->last = ngx_http_v2_write_value(b->last, header[i].value.data, + header[i].value.len, tmp); + +#if (NGX_DEBUG) + if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) { + ngx_strlow(tmp, header[i].key.data, header[i].key.len); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: \"%*s: %V\"", + header[i].key.len, tmp, &header[i].value); + } +#endif + } + } + + /* update headers frame length */ + + len = b->last - headers_frame - sizeof(ngx_http_grpc_frame_t); + + if (len > NGX_HTTP_V2_DEFAULT_FRAME_SIZE) { + len = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; + next = 1; + + } else { + next = 0; + } + + f = (ngx_http_grpc_frame_t *) headers_frame; + + f->length_0 = (u_char) ((len >> 16) & 0xff); + f->length_1 = (u_char) ((len >> 8) & 0xff); + f->length_2 = (u_char) (len & 0xff); + + /* create additional continuation frames */ + + p = headers_frame; + + while (next) { + p += sizeof(ngx_http_grpc_frame_t) + NGX_HTTP_V2_DEFAULT_FRAME_SIZE; + len = b->last - p; + + ngx_memmove(p + sizeof(ngx_http_grpc_frame_t), p, len); + b->last += sizeof(ngx_http_grpc_frame_t); + + if (len > NGX_HTTP_V2_DEFAULT_FRAME_SIZE) { + len = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; + next = 1; + + } else { + next = 0; + } + + f = (ngx_http_grpc_frame_t *) p; + + f->length_0 = (u_char) ((len >> 16) & 0xff); + f->length_1 = (u_char) ((len >> 8) & 0xff); + f->length_2 = (u_char) (len & 0xff); + f->type = NGX_HTTP_V2_CONTINUATION_FRAME; + f->flags = 0; + f->stream_id_0 = 0; + f->stream_id_1 = 0; + f->stream_id_2 = 0; + f->stream_id_3 = 1; + } + + f->flags |= NGX_HTTP_V2_END_HEADERS_FLAG; + +#if (NGX_DEBUG) + if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) { + u_char buf[512]; + size_t n, m; + + n = ngx_min(b->last - b->pos, 256); + m = ngx_hex_dump(buf, b->pos, n) - buf; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: %*s%s, len: %uz", + m, buf, b->last - b->pos > 256 ? "..." : "", + b->last - b->pos); + } +#endif + + if (r->request_body_no_buffering) { + + u->request_bufs = cl; + + } else { + + body = u->request_bufs; + u->request_bufs = cl; + + if (body == NULL) { + f = (ngx_http_grpc_frame_t *) headers_frame; + f->flags |= NGX_HTTP_V2_END_STREAM_FLAG; + } + + while (body) { + b = ngx_alloc_buf(r->pool); + if (b == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(b, body->buf, sizeof(ngx_buf_t)); + + cl->next = ngx_alloc_chain_link(r->pool); + if (cl->next == NULL) { + return NGX_ERROR; + } + + cl = cl->next; + cl->buf = b; + + body = body->next; + } + + b->last_buf = 1; + } + + u->output.output_filter = ngx_http_grpc_body_output_filter; + u->output.filter_ctx = r; + + b->flush = 1; + cl->next = NULL; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_grpc_reinit_request(ngx_http_request_t *r) +{ + ngx_http_grpc_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_grpc_module); + + if (ctx == NULL) { + return NGX_OK; + } + + ctx->state = 0; + ctx->header_sent = 0; + ctx->output_closed = 0; + ctx->parsing_headers = 0; + ctx->end_stream = 0; + ctx->status = 0; + ctx->connection = NULL; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_grpc_body_output_filter(void *data, ngx_chain_t *in) +{ + ngx_http_request_t *r = data; + + off_t file_pos; + u_char *p, *pos, *start; + size_t len, limit; + ngx_buf_t *b; + ngx_int_t rc; + ngx_uint_t next, last; + ngx_chain_t *cl, *out, **ll; + ngx_http_grpc_ctx_t *ctx; + ngx_http_grpc_frame_t *f; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc output filter"); + + ctx = ngx_http_grpc_get_ctx(r); + + if (ctx == NULL) { + return NGX_ERROR; + } + + if (in) { + if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) { + return NGX_ERROR; + } + } + + out = NULL; + ll = &out; + + if (!ctx->header_sent) { + /* first buffer contains headers */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc output header"); + + ctx->header_sent = 1; + + if (ctx->id != 1) { + /* + * keepalive connection: skip connection preface, + * update stream identifiers + */ + + b = ctx->in->buf; + b->pos += sizeof(ngx_http_grpc_connection_start) - 1; + + p = b->pos; + + while (p < b->last) { + f = (ngx_http_grpc_frame_t *) p; + p += sizeof(ngx_http_grpc_frame_t); + + f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff); + f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff); + f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff); + f->stream_id_3 = (u_char) (ctx->id & 0xff); + + p += (f->length_0 << 16) + (f->length_1 << 8) + f->length_2; + } + } + + if (ctx->in->buf->last_buf) { + ctx->output_closed = 1; + } + + *ll = ctx->in; + ll = &ctx->in->next; + + ctx->in = ctx->in->next; + } + + if (ctx->out) { + /* queued control frames */ + + *ll = ctx->out; + + for (cl = ctx->out, ll = &cl->next; cl; cl = cl->next) { + ll = &cl->next; + } + + ctx->out = NULL; + } + + f = NULL; + last = 0; + + limit = ngx_max(0, ctx->send_window); + + if (limit > ctx->connection->send_window) { + limit = ctx->connection->send_window; + } + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc output limit: %uz w:%z:%uz", + limit, ctx->send_window, ctx->connection->send_window); + +#if (NGX_SUPPRESS_WARN) + file_pos = 0; + pos = NULL; + cl = NULL; +#endif + + in = ctx->in; + + while (in && limit > 0) { + + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, + "grpc output in l:%d f:%d %p, pos %p, size: %z " + "file: %O, size: %O", + in->buf->last_buf, + in->buf->in_file, + in->buf->start, in->buf->pos, + in->buf->last - in->buf->pos, + in->buf->file_pos, + in->buf->file_last - in->buf->file_pos); + + if (ngx_buf_special(in->buf)) { + goto next; + } + + if (in->buf->in_file) { + file_pos = in->buf->file_pos; + + } else { + pos = in->buf->pos; + } + + next = 0; + + do { + + cl = ngx_http_grpc_get_buf(r, ctx); + if (cl == NULL) { + return NGX_ERROR; + } + + b = cl->buf; + + f = (ngx_http_grpc_frame_t *) b->last; + b->last += sizeof(ngx_http_grpc_frame_t); + + *ll = cl; + ll = &cl->next; + + cl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (cl == NULL) { + return NGX_ERROR; + } + + b = cl->buf; + start = b->start; + + ngx_memcpy(b, in->buf, sizeof(ngx_buf_t)); + + /* + * restore b->start to preserve memory allocated in the buffer, + * to reuse it later for headers and control frames + */ + + b->start = start; + + if (in->buf->in_file) { + b->file_pos = file_pos; + file_pos += ngx_min(NGX_HTTP_V2_DEFAULT_FRAME_SIZE, limit); + + if (file_pos >= in->buf->file_last) { + file_pos = in->buf->file_last; + next = 1; + } + + b->file_last = file_pos; + len = (ngx_uint_t) (file_pos - b->file_pos); + + } else { + b->pos = pos; + pos += ngx_min(NGX_HTTP_V2_DEFAULT_FRAME_SIZE, limit); + + if (pos >= in->buf->last) { + pos = in->buf->last; + next = 1; + } + + b->last = pos; + len = (ngx_uint_t) (pos - b->pos); + } + + b->tag = (ngx_buf_tag_t) &ngx_http_grpc_body_output_filter; + b->shadow = in->buf; + b->last_shadow = next; + + b->last_buf = 0; + b->last_in_chain = 0; + + *ll = cl; + ll = &cl->next; + + f->length_0 = (u_char) ((len >> 16) & 0xff); + f->length_1 = (u_char) ((len >> 8) & 0xff); + f->length_2 = (u_char) (len & 0xff); + f->type = NGX_HTTP_V2_DATA_FRAME; + f->flags = 0; + f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff); + f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff); + f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff); + f->stream_id_3 = (u_char) (ctx->id & 0xff); + + limit -= len; + ctx->send_window -= len; + ctx->connection->send_window -= len; + + } while (!next && limit > 0); + + if (!next) { + /* + * if the buffer wasn't fully sent due to flow control limits, + * preserve position for future use + */ + + if (in->buf->in_file) { + in->buf->file_pos = file_pos; + + } else { + in->buf->pos = pos; + } + + break; + } + + next: + + if (in->buf->last_buf) { + last = 1; + } + + in = in->next; + } + + ctx->in = in; + + if (last) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc output last"); + + ctx->output_closed = 1; + + if (f) { + f->flags |= NGX_HTTP_V2_END_STREAM_FLAG; + + } else { + cl = ngx_http_grpc_get_buf(r, ctx); + if (cl == NULL) { + return NGX_ERROR; + } + + b = cl->buf; + + f = (ngx_http_grpc_frame_t *) b->last; + b->last += sizeof(ngx_http_grpc_frame_t); + + f->length_0 = 0; + f->length_1 = 0; + f->length_2 = 0; + f->type = NGX_HTTP_V2_DATA_FRAME; + f->flags = NGX_HTTP_V2_END_STREAM_FLAG; + f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff); + f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff); + f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff); + f->stream_id_3 = (u_char) (ctx->id & 0xff); + + *ll = cl; + ll = &cl->next; + } + + cl->buf->last_buf = 1; + } + + *ll = NULL; + +#if (NGX_DEBUG) + + for (cl = out; cl; cl = cl->next) { + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, + "grpc output out l:%d f:%d %p, pos %p, size: %z " + "file: %O, size: %O", + cl->buf->last_buf, + cl->buf->in_file, + cl->buf->start, cl->buf->pos, + cl->buf->last - cl->buf->pos, + cl->buf->file_pos, + cl->buf->file_last - cl->buf->file_pos); + } + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc output limit: %uz w:%z:%uz", + limit, ctx->send_window, ctx->connection->send_window); + +#endif + + rc = ngx_chain_writer(&r->upstream->writer, out); + + ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, + (ngx_buf_tag_t) &ngx_http_grpc_body_output_filter); + + for (cl = ctx->free; cl; cl = cl->next) { + + /* mark original buffers as sent */ + + if (cl->buf->shadow) { + if (cl->buf->last_shadow) { + b = cl->buf->shadow; + b->pos = b->last; + } + + cl->buf->shadow = NULL; + } + } + + if (rc == NGX_OK && ctx->in) { + rc = NGX_AGAIN; + } + + return rc; +} + + +static ngx_int_t +ngx_http_grpc_process_header(ngx_http_request_t *r) +{ + ngx_str_t *status_line; + ngx_int_t rc, status; + ngx_buf_t *b; + ngx_table_elt_t *h; + ngx_http_upstream_t *u; + ngx_http_grpc_ctx_t *ctx; + ngx_http_upstream_header_t *hh; + ngx_http_upstream_main_conf_t *umcf; + + u = r->upstream; + b = &u->buffer; + +#if (NGX_DEBUG) + if (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP) { + u_char buf[512]; + size_t n, m; + + n = ngx_min(b->last - b->pos, 256); + m = ngx_hex_dump(buf, b->pos, n) - buf; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc response: %*s%s, len: %uz", + m, buf, b->last - b->pos > 256 ? "..." : "", + b->last - b->pos); + } +#endif + + ctx = ngx_http_grpc_get_ctx(r); + + if (ctx == NULL) { + return NGX_ERROR; + } + + umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); + + for ( ;; ) { + + if (ctx->state < ngx_http_grpc_st_payload) { + + rc = ngx_http_grpc_parse_frame(r, ctx, b); + + if (rc == NGX_AGAIN) { + + /* + * there can be a lot of window update frames, + * so we reset buffer if it is empty and we haven't + * started parsing headers yet + */ + + if (!ctx->parsing_headers) { + b->pos = b->start; + b->last = b->pos; + } + + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + /* + * RFC 7540 says that implementations MUST discard frames + * that have unknown or unsupported types. However, extension + * frames that appear in the middle of a header block are + * not permitted. Also, for obvious reasons CONTINUATION frames + * cannot appear before headers, and DATA frames are not expected + * to appear before all headers are parsed. + */ + + if (ctx->type == NGX_HTTP_V2_DATA_FRAME + || (ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME + && !ctx->parsing_headers) + || (ctx->type != NGX_HTTP_V2_CONTINUATION_FRAME + && ctx->parsing_headers)) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent unexpected http2 frame: %d", + ctx->type); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (ctx->stream_id && ctx->stream_id != ctx->id) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent frame for unknown stream %ui", + ctx->stream_id); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + } + + /* frame payload */ + + if (ctx->type == NGX_HTTP_V2_RST_STREAM_FRAME) { + + rc = ngx_http_grpc_parse_rst_stream(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream rejected request with error %ui", + ctx->error); + + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (ctx->type == NGX_HTTP_V2_GOAWAY_FRAME) { + + rc = ngx_http_grpc_parse_goaway(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + /* + * If stream_id is lower than one we use, our + * request won't be processed and needs to be retried. + * If stream_id is greater or equal to the one we use, + * we can continue normally (except we can't use this + * connection for additional requests). If there is + * a real error, the connection will be closed. + */ + + if (ctx->stream_id < ctx->id) { + + /* TODO: we can retry non-idempotent requests */ + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent goaway with error %ui", + ctx->error); + + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + continue; + } + + if (ctx->type == NGX_HTTP_V2_WINDOW_UPDATE_FRAME) { + + rc = ngx_http_grpc_parse_window_update(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (ctx->in) { + ngx_post_event(u->peer.connection->write, &ngx_posted_events); + } + + continue; + } + + if (ctx->type == NGX_HTTP_V2_SETTINGS_FRAME) { + + rc = ngx_http_grpc_parse_settings(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (ctx->in) { + ngx_post_event(u->peer.connection->write, &ngx_posted_events); + } + + continue; + } + + if (ctx->type == NGX_HTTP_V2_PING_FRAME) { + + rc = ngx_http_grpc_parse_ping(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + ngx_post_event(u->peer.connection->write, &ngx_posted_events); + continue; + } + + if (ctx->type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent unexpected push promise frame"); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (ctx->type != NGX_HTTP_V2_HEADERS_FRAME + && ctx->type != NGX_HTTP_V2_CONTINUATION_FRAME) + { + /* priority, unknown frames */ + + if (b->last - b->pos < (ssize_t) ctx->rest) { + ctx->rest -= b->last - b->pos; + b->pos = b->last; + return NGX_AGAIN; + } + + b->pos += ctx->rest; + ctx->rest = 0; + ctx->state = ngx_http_grpc_st_start; + + continue; + } + + /* headers */ + + for ( ;; ) { + + rc = ngx_http_grpc_parse_header(r, ctx, b); + + if (rc == NGX_AGAIN) { + break; + } + + if (rc == NGX_OK) { + + /* a header line has been parsed successfully */ + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: \"%V: %V\"", + &ctx->name, &ctx->value); + + if (ctx->name.len && ctx->name.data[0] == ':') { + + if (ctx->name.len != sizeof(":status") - 1 + || ngx_strncmp(ctx->name.data, ":status", + sizeof(":status") - 1) + != 0) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid header \"%V: %V\"", + &ctx->name, &ctx->value); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (ctx->status) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent duplicate :status header"); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + status_line = &ctx->value; + + if (status_line->len != 3) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid :status \"%V\"", + status_line); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + status = ngx_atoi(status_line->data, 3); + + if (status == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid :status \"%V\"", + status_line); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (status < NGX_HTTP_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent unexpected :status \"%V\"", + status_line); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + u->headers_in.status_n = status; + + if (u->state && u->state->status == 0) { + u->state->status = status; + } + + ctx->status = 1; + + continue; + + } else if (!ctx->status) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent no :status header"); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + h = ngx_list_push(&u->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key = ctx->name; + h->value = ctx->value; + h->lowcase_key = h->key.data; + h->hash = ngx_hash_key(h->key.data, h->key.len); + + hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + return NGX_ERROR; + } + + continue; + } + + if (rc == NGX_HTTP_PARSE_HEADER_DONE) { + + /* a whole header has been parsed successfully */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header done"); + + if (ctx->end_stream) { + u->headers_in.content_length_n = 0; + + if (ctx->in == NULL + && ctx->out == NULL + && ctx->output_closed + && b->last == b->pos) + { + u->keepalive = 1; + } + } + + return NGX_OK; + } + + /* there was error while a header line parsing */ + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid header"); + + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + /* rc == NGX_AGAIN */ + + if (ctx->rest == 0) { + ctx->state = ngx_http_grpc_st_start; + continue; + } + + return NGX_AGAIN; + } +} + + +static ngx_int_t +ngx_http_grpc_filter_init(void *data) +{ + ngx_http_grpc_ctx_t *ctx = data; + + ngx_http_request_t *r; + ngx_http_upstream_t *u; + + r = ctx->request; + u = r->upstream; + + u->length = 1; + + if (ctx->end_stream) { + u->length = 0; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_grpc_filter(void *data, ssize_t bytes) +{ + ngx_http_grpc_ctx_t *ctx = data; + + ngx_int_t rc; + ngx_buf_t *b, *buf; + ngx_chain_t *cl, **ll; + ngx_table_elt_t *h; + ngx_http_request_t *r; + ngx_http_upstream_t *u; + + r = ctx->request; + u = r->upstream; + b = &u->buffer; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc filter bytes:%z", bytes); + + b->pos = b->last; + b->last += bytes; + + for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { + ll = &cl->next; + } + + for ( ;; ) { + + if (ctx->state < ngx_http_grpc_st_payload) { + + rc = ngx_http_grpc_parse_frame(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if ((ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME + && !ctx->parsing_headers) + || (ctx->type != NGX_HTTP_V2_CONTINUATION_FRAME + && ctx->parsing_headers)) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent unexpected http2 frame: %d", + ctx->type); + return NGX_ERROR; + } + + if (ctx->type == NGX_HTTP_V2_DATA_FRAME) { + + if (ctx->stream_id != ctx->id) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent data frame " + "for unknown stream %ui", + ctx->stream_id); + return NGX_ERROR; + } + + if (ctx->rest > ctx->recv_window) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream violated stream flow control, " + "received %uz data frame with window %uz", + ctx->rest, ctx->recv_window); + return NGX_ERROR; + } + + if (ctx->rest > ctx->connection->recv_window) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream violated connection flow control, " + "received %uz data frame with window %uz", + ctx->rest, ctx->connection->recv_window); + return NGX_ERROR; + } + + ctx->recv_window -= ctx->rest; + ctx->connection->recv_window -= ctx->rest; + + if (ctx->connection->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4 + || ctx->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4) + { + if (ngx_http_grpc_send_window_update(r, ctx) != NGX_OK) { + return NGX_ERROR; + } + + ngx_post_event(u->peer.connection->write, + &ngx_posted_events); + } + } + + if (ctx->stream_id && ctx->stream_id != ctx->id) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent frame for unknown stream %ui", + ctx->stream_id); + return NGX_ERROR; + } + + ctx->padding = 0; + } + + if (ctx->state == ngx_http_grpc_st_padding) { + + if (b->last - b->pos < (ssize_t) ctx->rest) { + ctx->rest -= b->last - b->pos; + b->pos = b->last; + return NGX_AGAIN; + } + + b->pos += ctx->rest; + ctx->rest = 0; + ctx->state = ngx_http_grpc_st_start; + + if (ctx->flags & NGX_HTTP_V2_END_STREAM_FLAG) { + u->length = 0; + + if (ctx->in == NULL + && ctx->out == NULL + && ctx->output_closed + && b->last == b->pos) + { + u->keepalive = 1; + } + + break; + } + + continue; + } + + /* frame payload */ + + if (ctx->type == NGX_HTTP_V2_RST_STREAM_FRAME) { + + rc = ngx_http_grpc_parse_rst_stream(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream rejected request with error %ui", + ctx->error); + + return NGX_ERROR; + } + + if (ctx->type == NGX_HTTP_V2_GOAWAY_FRAME) { + + rc = ngx_http_grpc_parse_goaway(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + /* + * If stream_id is lower than one we use, our + * request won't be processed and needs to be retried. + * If stream_id is greater or equal to the one we use, + * we can continue normally (except we can't use this + * connection for additional requests). If there is + * a real error, the connection will be closed. + */ + + if (ctx->stream_id < ctx->id) { + + /* TODO: we can retry non-idempotent requests */ + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent goaway with error %ui", + ctx->error); + + return NGX_ERROR; + } + + continue; + } + + if (ctx->type == NGX_HTTP_V2_WINDOW_UPDATE_FRAME) { + + rc = ngx_http_grpc_parse_window_update(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (ctx->in) { + ngx_post_event(u->peer.connection->write, &ngx_posted_events); + } + + continue; + } + + if (ctx->type == NGX_HTTP_V2_SETTINGS_FRAME) { + + rc = ngx_http_grpc_parse_settings(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (ctx->in) { + ngx_post_event(u->peer.connection->write, &ngx_posted_events); + } + + continue; + } + + if (ctx->type == NGX_HTTP_V2_PING_FRAME) { + + rc = ngx_http_grpc_parse_ping(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + ngx_post_event(u->peer.connection->write, &ngx_posted_events); + continue; + } + + if (ctx->type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent unexpected push promise frame"); + return NGX_ERROR; + } + + if (ctx->type == NGX_HTTP_V2_HEADERS_FRAME + || ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME) + { + for ( ;; ) { + + rc = ngx_http_grpc_parse_header(r, ctx, b); + + if (rc == NGX_AGAIN) { + break; + } + + if (rc == NGX_OK) { + + /* a header line has been parsed successfully */ + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc trailer: \"%V: %V\"", + &ctx->name, &ctx->value); + + if (ctx->name.len && ctx->name.data[0] == ':') { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid " + "trailer \"%V: %V\"", + &ctx->name, &ctx->value); + return NGX_ERROR; + } + + h = ngx_list_push(&u->headers_in.trailers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key = ctx->name; + h->value = ctx->value; + h->lowcase_key = h->key.data; + h->hash = ngx_hash_key(h->key.data, h->key.len); + + continue; + } + + if (rc == NGX_HTTP_PARSE_HEADER_DONE) { + + /* a whole header has been parsed successfully */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc trailer done"); + + if (ctx->end_stream) { + u->length = 0; + + if (ctx->in == NULL + && ctx->out == NULL + && ctx->output_closed + && b->last == b->pos) + { + u->keepalive = 1; + } + + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent trailer without " + "end stream flag"); + return NGX_ERROR; + } + + /* there was error while a header line parsing */ + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid trailer"); + + return NGX_ERROR; + } + + /* rc == NGX_AGAIN */ + + if (ctx->rest == 0) { + ctx->state = ngx_http_grpc_st_start; + continue; + } + + return NGX_AGAIN; + } + + if (ctx->type != NGX_HTTP_V2_DATA_FRAME) { + + /* priority, unknown frames */ + + if (b->last - b->pos < (ssize_t) ctx->rest) { + ctx->rest -= b->last - b->pos; + b->pos = b->last; + return NGX_AGAIN; + } + + b->pos += ctx->rest; + ctx->rest = 0; + ctx->state = ngx_http_grpc_st_start; + + continue; + } + + /* + * data frame: + * + * +---------------+ + * |Pad Length? (8)| + * +---------------+-----------------------------------------------+ + * | Data (*) ... + * +---------------------------------------------------------------+ + * | Padding (*) ... + * +---------------------------------------------------------------+ + */ + + if (ctx->flags & NGX_HTTP_V2_PADDED_FLAG) { + + if (ctx->rest == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too short http2 frame"); + return NGX_ERROR; + } + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ctx->flags &= ~NGX_HTTP_V2_PADDED_FLAG; + ctx->padding = *b->pos++; + ctx->rest -= 1; + + if (ctx->padding > ctx->rest) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent http2 frame with too long " + "padding: %d in frame %uz", + ctx->padding, ctx->rest); + return NGX_ERROR; + } + + continue; + } + + if (ctx->rest == ctx->padding) { + goto done; + } + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs); + if (cl == NULL) { + return NGX_ERROR; + } + + *ll = cl; + ll = &cl->next; + + buf = cl->buf; + + buf->flush = 1; + buf->memory = 1; + + buf->pos = b->pos; + buf->tag = u->output.tag; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc output buf %p", buf->pos); + + if (b->last - b->pos < (ssize_t) ctx->rest - ctx->padding) { + + ctx->rest -= b->last - b->pos; + b->pos = b->last; + buf->last = b->pos; + + return NGX_AGAIN; + } + + b->pos += ctx->rest - ctx->padding; + buf->last = b->pos; + ctx->rest = ctx->padding; + + done: + + if (ctx->padding) { + ctx->state = ngx_http_grpc_st_padding; + continue; + } + + ctx->state = ngx_http_grpc_st_start; + + if (ctx->flags & NGX_HTTP_V2_END_STREAM_FLAG) { + u->length = 0; + + if (ctx->in == NULL + && ctx->out == NULL + && ctx->output_closed + && b->last == b->pos) + { + u->keepalive = 1; + } + + break; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_grpc_parse_frame(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, + ngx_buf_t *b) +{ + u_char ch, *p; + ngx_http_grpc_state_e state; + + state = ctx->state; + + for (p = b->pos; p < b->last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc frame byte: %02Xd, s:%d", ch, state); +#endif + + switch (state) { + + case ngx_http_grpc_st_start: + ctx->rest = ch << 16; + state = ngx_http_grpc_st_length_2; + break; + + case ngx_http_grpc_st_length_2: + ctx->rest |= ch << 8; + state = ngx_http_grpc_st_length_3; + break; + + case ngx_http_grpc_st_length_3: + ctx->rest |= ch; + + if (ctx->rest > NGX_HTTP_V2_DEFAULT_FRAME_SIZE) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 frame: %uz", + ctx->rest); + return NGX_ERROR; + } + + state = ngx_http_grpc_st_type; + break; + + case ngx_http_grpc_st_type: + ctx->type = ch; + state = ngx_http_grpc_st_flags; + break; + + case ngx_http_grpc_st_flags: + ctx->flags = ch; + state = ngx_http_grpc_st_stream_id; + break; + + case ngx_http_grpc_st_stream_id: + ctx->stream_id = (ch & 0x7f) << 24; + state = ngx_http_grpc_st_stream_id_2; + break; + + case ngx_http_grpc_st_stream_id_2: + ctx->stream_id |= ch << 16; + state = ngx_http_grpc_st_stream_id_3; + break; + + case ngx_http_grpc_st_stream_id_3: + ctx->stream_id |= ch << 8; + state = ngx_http_grpc_st_stream_id_4; + break; + + case ngx_http_grpc_st_stream_id_4: + ctx->stream_id |= ch; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc frame: %d, len: %uz, f:%d, i:%ui", + ctx->type, ctx->rest, ctx->flags, ctx->stream_id); + + b->pos = p + 1; + + ctx->state = ngx_http_grpc_st_payload; + ctx->frame_state = 0; + + return NGX_OK; + + /* suppress warning */ + case ngx_http_grpc_st_payload: + case ngx_http_grpc_st_padding: + break; + } + } + + b->pos = p; + ctx->state = state; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_grpc_parse_header(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, + ngx_buf_t *b) +{ + u_char ch, *p, *last; + size_t min; + ngx_int_t rc; + enum { + sw_start = 0, + sw_padding_length, + sw_dependency, + sw_dependency_2, + sw_dependency_3, + sw_dependency_4, + sw_weight, + sw_fragment, + sw_padding + } state; + + state = ctx->frame_state; + + if (state == sw_start) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc parse header: start"); + + if (ctx->type == NGX_HTTP_V2_HEADERS_FRAME) { + ctx->parsing_headers = 1; + ctx->fragment_state = 0; + + min = (ctx->flags & NGX_HTTP_V2_PADDED_FLAG ? 1 : 0) + + (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG ? 5 : 0); + + if (ctx->rest < min) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent headers frame " + "with invalid length: %uz", + ctx->rest); + return NGX_ERROR; + } + + if (ctx->flags & NGX_HTTP_V2_END_STREAM_FLAG) { + ctx->end_stream = 1; + } + + if (ctx->flags & NGX_HTTP_V2_PADDED_FLAG) { + state = sw_padding_length; + + } else if (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG) { + state = sw_dependency; + + } else { + state = sw_fragment; + } + + } else if (ctx->type == NGX_HTTP_V2_CONTINUATION_FRAME) { + state = sw_fragment; + } + + ctx->padding = 0; + ctx->frame_state = state; + } + + if (state < sw_fragment) { + + if (b->last - b->pos < (ssize_t) ctx->rest) { + last = b->last; + + } else { + last = b->pos + ctx->rest; + } + + for (p = b->pos; p < last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header byte: %02Xd s:%d", ch, state); +#endif + + /* + * headers frame: + * + * +---------------+ + * |Pad Length? (8)| + * +-+-------------+----------------------------------------------+ + * |E| Stream Dependency? (31) | + * +-+-------------+----------------------------------------------+ + * | Weight? (8) | + * +-+-------------+----------------------------------------------+ + * | Header Block Fragment (*) ... + * +--------------------------------------------------------------+ + * | Padding (*) ... + * +--------------------------------------------------------------+ + */ + + switch (state) { + + case sw_padding_length: + + ctx->padding = ch; + + if (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG) { + state = sw_dependency; + break; + } + + goto fragment; + + case sw_dependency: + state = sw_dependency_2; + break; + + case sw_dependency_2: + state = sw_dependency_3; + break; + + case sw_dependency_3: + state = sw_dependency_4; + break; + + case sw_dependency_4: + state = sw_weight; + break; + + case sw_weight: + goto fragment; + + /* suppress warning */ + case sw_start: + case sw_fragment: + case sw_padding: + break; + } + } + + ctx->rest -= p - b->pos; + b->pos = p; + + ctx->frame_state = state; + return NGX_AGAIN; + + fragment: + + p++; + ctx->rest -= p - b->pos; + b->pos = p; + + if (ctx->padding > ctx->rest) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent http2 frame with too long " + "padding: %d in frame %uz", + ctx->padding, ctx->rest); + return NGX_ERROR; + } + + state = sw_fragment; + ctx->frame_state = state; + } + + if (state == sw_fragment) { + + rc = ngx_http_grpc_parse_fragment(r, ctx, b); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + return NGX_OK; + } + + /* rc == NGX_DONE */ + + state = sw_padding; + ctx->frame_state = state; + } + + if (state == sw_padding) { + + if (b->last - b->pos < (ssize_t) ctx->rest) { + + ctx->rest -= b->last - b->pos; + b->pos = b->last; + + return NGX_AGAIN; + } + + b->pos += ctx->rest; + ctx->rest = 0; + + ctx->state = ngx_http_grpc_st_start; + + if (ctx->flags & NGX_HTTP_V2_END_HEADERS_FLAG) { + + if (ctx->fragment_state) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent truncated http2 header"); + return NGX_ERROR; + } + + ctx->parsing_headers = 0; + + return NGX_HTTP_PARSE_HEADER_DONE; + } + + return NGX_AGAIN; + } + + /* unreachable */ + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_grpc_parse_fragment(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, + ngx_buf_t *b) +{ + u_char ch, *p, *last; + size_t size; + ngx_uint_t index, size_update; + enum { + sw_start = 0, + sw_index, + sw_name_length, + sw_name_length_2, + sw_name_length_3, + sw_name_length_4, + sw_name, + sw_name_bytes, + sw_value_length, + sw_value_length_2, + sw_value_length_3, + sw_value_length_4, + sw_value, + sw_value_bytes + } state; + + /* header block fragment */ + +#if 0 + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header fragment %p:%p rest:%uz", + b->pos, b->last, ctx->rest); +#endif + + if (b->last - b->pos < (ssize_t) ctx->rest - ctx->padding) { + last = b->last; + + } else { + last = b->pos + ctx->rest - ctx->padding; + } + + state = ctx->fragment_state; + + for (p = b->pos; p < last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header byte: %02Xd s:%d", ch, state); +#endif + + switch (state) { + + case sw_start: + ctx->index = 0; + + if ((ch & 0x80) == 0x80) { + /* + * indexed header: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 1 | Index (7+) | + * +---+---------------------------+ + */ + + index = ch & ~0x80; + + if (index == 0 || index > 61) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid http2 " + "table index: %ui", index); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc indexed header: %ui", index); + + ctx->index = index; + ctx->literal = 0; + + goto done; + + } else if ((ch & 0xc0) == 0x40) { + /* + * literal header with incremental indexing: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 1 | Index (6+) | + * +---+---+-----------------------+ + * | H | Value Length (7+) | + * +---+---------------------------+ + * | Value String (Length octets) | + * +-------------------------------+ + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 1 | 0 | + * +---+---+-----------------------+ + * | H | Name Length (7+) | + * +---+---------------------------+ + * | Name String (Length octets) | + * +---+---------------------------+ + * | H | Value Length (7+) | + * +---+---------------------------+ + * | Value String (Length octets) | + * +-------------------------------+ + */ + + index = ch & ~0xc0; + + if (index > 61) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid http2 " + "table index: %ui", index); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc literal header: %ui", index); + + if (index == 0) { + state = sw_name_length; + break; + } + + ctx->index = index; + ctx->literal = 1; + + state = sw_value_length; + break; + + } else if ((ch & 0xe0) == 0x20) { + /* + * dynamic table size update: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 0 | 1 | Max size (5+) | + * +---+---------------------------+ + */ + + size_update = ch & ~0xe0; + + if (size_update > 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid http2 " + "dynamic table size update: %ui", + size_update); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc table size update: %ui", size_update); + + break; + + } else if ((ch & 0xf0) == 0x10) { + /* + * literal header field never indexed: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 0 | 0 | 1 | Index (4+) | + * +---+---+-----------------------+ + * | H | Value Length (7+) | + * +---+---------------------------+ + * | Value String (Length octets) | + * +-------------------------------+ + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 0 | 0 | 1 | 0 | + * +---+---+-----------------------+ + * | H | Name Length (7+) | + * +---+---------------------------+ + * | Name String (Length octets) | + * +---+---------------------------+ + * | H | Value Length (7+) | + * +---+---------------------------+ + * | Value String (Length octets) | + * +-------------------------------+ + */ + + index = ch & ~0xf0; + + if (index == 0x0f) { + ctx->index = index; + ctx->literal = 1; + state = sw_index; + break; + } + + if (index == 0) { + state = sw_name_length; + break; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc literal header never indexed: %ui", + index); + + ctx->index = index; + ctx->literal = 1; + + state = sw_value_length; + break; + + } else if ((ch & 0xf0) == 0x00) { + /* + * literal header field without indexing: + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 0 | 0 | 0 | Index (4+) | + * +---+---+-----------------------+ + * | H | Value Length (7+) | + * +---+---------------------------+ + * | Value String (Length octets) | + * +-------------------------------+ + * + * 0 1 2 3 4 5 6 7 + * +---+---+---+---+---+---+---+---+ + * | 0 | 0 | 0 | 0 | 0 | + * +---+---+-----------------------+ + * | H | Name Length (7+) | + * +---+---------------------------+ + * | Name String (Length octets) | + * +---+---------------------------+ + * | H | Value Length (7+) | + * +---+---------------------------+ + * | Value String (Length octets) | + * +-------------------------------+ + */ + + index = ch & ~0xf0; + + if (index == 0x0f) { + ctx->index = index; + ctx->literal = 1; + state = sw_index; + break; + } + + if (index == 0) { + state = sw_name_length; + break; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc literal header without indexing: %ui", + index); + + ctx->index = index; + ctx->literal = 1; + + state = sw_value_length; + break; + } + + /* not reached */ + + return NGX_ERROR; + + case sw_index: + ctx->index = ctx->index + (ch & ~0x80); + + if (ch & 0x80) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent http2 table index " + "with continuation flag"); + return NGX_ERROR; + } + + if (ctx->index > 61) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid http2 " + "table index: %ui", ctx->index); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header index: %ui", ctx->index); + + state = sw_value_length; + break; + + case sw_name_length: + ctx->field_huffman = ch & 0x80 ? 1 : 0; + ctx->field_length = ch & ~0x80; + + if (ctx->field_length == 0x7f) { + state = sw_name_length_2; + break; + } + + if (ctx->field_length == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent zero http2 " + "header name length"); + return NGX_ERROR; + } + + state = sw_name; + break; + + case sw_name_length_2: + ctx->field_length += ch & ~0x80; + + if (ch & 0x80) { + state = sw_name_length_3; + break; + } + + state = sw_name; + break; + + case sw_name_length_3: + ctx->field_length += (ch & ~0x80) << 7; + + if (ch & 0x80) { + state = sw_name_length_4; + break; + } + + state = sw_name; + break; + + case sw_name_length_4: + ctx->field_length += (ch & ~0x80) << 14; + + if (ch & 0x80) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 " + "header name length"); + return NGX_ERROR; + } + + state = sw_name; + break; + + case sw_name: + ctx->name.len = ctx->field_huffman ? + ctx->field_length * 8 / 5 : ctx->field_length; + + ctx->name.data = ngx_pnalloc(r->pool, ctx->name.len + 1); + if (ctx->name.data == NULL) { + return NGX_ERROR; + } + + ctx->field_end = ctx->name.data; + ctx->field_rest = ctx->field_length; + ctx->field_state = 0; + + state = sw_name_bytes; + + /* fall through */ + + case sw_name_bytes: + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc name: len:%uz h:%d last:%uz, rest:%uz", + ctx->field_length, + ctx->field_huffman, + last - p, + ctx->rest - (p - b->pos)); + + size = ngx_min(last - p, (ssize_t) ctx->field_rest); + ctx->field_rest -= size; + + if (ctx->field_huffman) { + if (ngx_http_v2_huff_decode(&ctx->field_state, p, size, + &ctx->field_end, + ctx->field_rest == 0, + r->connection->log) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid encoded header"); + return NGX_ERROR; + } + + ctx->name.len = ctx->field_end - ctx->name.data; + ctx->name.data[ctx->name.len] = '\0'; + + } else { + ctx->field_end = ngx_cpymem(ctx->field_end, p, size); + ctx->name.data[ctx->name.len] = '\0'; + } + + p += size - 1; + + if (ctx->field_rest == 0) { + state = sw_value_length; + } + + break; + + case sw_value_length: + ctx->field_huffman = ch & 0x80 ? 1 : 0; + ctx->field_length = ch & ~0x80; + + if (ctx->field_length == 0x7f) { + state = sw_value_length_2; + break; + } + + if (ctx->field_length == 0) { + ngx_str_set(&ctx->value, ""); + goto done; + } + + state = sw_value; + break; + + case sw_value_length_2: + ctx->field_length += ch & ~0x80; + + if (ch & 0x80) { + state = sw_value_length_3; + break; + } + + state = sw_value; + break; + + case sw_value_length_3: + ctx->field_length += (ch & ~0x80) << 7; + + if (ch & 0x80) { + state = sw_value_length_4; + break; + } + + state = sw_value; + break; + + case sw_value_length_4: + ctx->field_length += (ch & ~0x80) << 14; + + if (ch & 0x80) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 " + "header value length"); + return NGX_ERROR; + } + + state = sw_value; + break; + + case sw_value: + ctx->value.len = ctx->field_huffman ? + ctx->field_length * 8 / 5 : ctx->field_length; + + ctx->value.data = ngx_pnalloc(r->pool, ctx->value.len + 1); + if (ctx->value.data == NULL) { + return NGX_ERROR; + } + + ctx->field_end = ctx->value.data; + ctx->field_rest = ctx->field_length; + ctx->field_state = 0; + + state = sw_value_bytes; + + /* fall through */ + + case sw_value_bytes: + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc value: len:%uz h:%d last:%uz, rest:%uz", + ctx->field_length, + ctx->field_huffman, + last - p, + ctx->rest - (p - b->pos)); + + size = ngx_min(last - p, (ssize_t) ctx->field_rest); + ctx->field_rest -= size; + + if (ctx->field_huffman) { + if (ngx_http_v2_huff_decode(&ctx->field_state, p, size, + &ctx->field_end, + ctx->field_rest == 0, + r->connection->log) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid encoded header"); + return NGX_ERROR; + } + + ctx->value.len = ctx->field_end - ctx->value.data; + ctx->value.data[ctx->value.len] = '\0'; + + } else { + ctx->field_end = ngx_cpymem(ctx->field_end, p, size); + ctx->value.data[ctx->value.len] = '\0'; + } + + p += size - 1; + + if (ctx->field_rest == 0) { + goto done; + } + + break; + } + + continue; + + done: + + p++; + ctx->rest -= p - b->pos; + ctx->fragment_state = sw_start; + b->pos = p; + + if (ctx->index) { + ctx->name = *ngx_http_v2_get_static_name(ctx->index); + } + + if (ctx->index && !ctx->literal) { + ctx->value = *ngx_http_v2_get_static_value(ctx->index); + } + + if (!ctx->index) { + if (ngx_http_grpc_validate_header_name(r, &ctx->name) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid header: \"%V: %V\"", + &ctx->name, &ctx->value); + return NGX_ERROR; + } + } + + if (!ctx->index || ctx->literal) { + if (ngx_http_grpc_validate_header_value(r, &ctx->value) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid header: \"%V: %V\"", + &ctx->name, &ctx->value); + return NGX_ERROR; + } + } + + return NGX_OK; + } + + ctx->rest -= p - b->pos; + ctx->fragment_state = state; + b->pos = p; + + if (ctx->rest > ctx->padding) { + return NGX_AGAIN; + } + + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_grpc_validate_header_name(ngx_http_request_t *r, ngx_str_t *s) +{ + u_char ch; + ngx_uint_t i; + + for (i = 0; i < s->len; i++) { + ch = s->data[i]; + + if (ch == ':' && i > 0) { + return NGX_ERROR; + } + + if (ch >= 'A' && ch <= 'Z') { + return NGX_ERROR; + } + + if (ch == '\0' || ch == CR || ch == LF) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_grpc_validate_header_value(ngx_http_request_t *r, ngx_str_t *s) +{ + u_char ch; + ngx_uint_t i; + + for (i = 0; i < s->len; i++) { + ch = s->data[i]; + + if (ch == '\0' || ch == CR || ch == LF) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_grpc_parse_rst_stream(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, + ngx_buf_t *b) +{ + u_char ch, *p, *last; + enum { + sw_start = 0, + sw_error_2, + sw_error_3, + sw_error_4 + } state; + + if (b->last - b->pos < (ssize_t) ctx->rest) { + last = b->last; + + } else { + last = b->pos + ctx->rest; + } + + state = ctx->frame_state; + + if (state == sw_start) { + if (ctx->rest != 4) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent rst stream frame " + "with invalid length: %uz", + ctx->rest); + return NGX_ERROR; + } + } + + for (p = b->pos; p < last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc rst byte: %02Xd s:%d", ch, state); +#endif + + switch (state) { + + case sw_start: + ctx->error = ch << 24; + state = sw_error_2; + break; + + case sw_error_2: + ctx->error |= ch << 16; + state = sw_error_3; + break; + + case sw_error_3: + ctx->error |= ch << 8; + state = sw_error_4; + break; + + case sw_error_4: + ctx->error |= ch; + state = sw_start; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc error: %ui", ctx->error); + + break; + } + } + + ctx->rest -= p - b->pos; + ctx->frame_state = state; + b->pos = p; + + if (ctx->rest > 0) { + return NGX_AGAIN; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_grpc_parse_goaway(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, + ngx_buf_t *b) +{ + u_char ch, *p, *last; + enum { + sw_start = 0, + sw_last_stream_id_2, + sw_last_stream_id_3, + sw_last_stream_id_4, + sw_error, + sw_error_2, + sw_error_3, + sw_error_4, + sw_debug + } state; + + if (b->last - b->pos < (ssize_t) ctx->rest) { + last = b->last; + + } else { + last = b->pos + ctx->rest; + } + + state = ctx->frame_state; + + if (state == sw_start) { + + if (ctx->stream_id) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent goaway frame " + "with non-zero stream id: %ui", + ctx->stream_id); + return NGX_ERROR; + } + + if (ctx->rest < 8) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent goaway frame " + "with invalid length: %uz", + ctx->rest); + return NGX_ERROR; + } + } + + for (p = b->pos; p < last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc goaway byte: %02Xd s:%d", ch, state); +#endif + + switch (state) { + + case sw_start: + ctx->stream_id = (ch & 0x7f) << 24; + state = sw_last_stream_id_2; + break; + + case sw_last_stream_id_2: + ctx->stream_id |= ch << 16; + state = sw_last_stream_id_3; + break; + + case sw_last_stream_id_3: + ctx->stream_id |= ch << 8; + state = sw_last_stream_id_4; + break; + + case sw_last_stream_id_4: + ctx->stream_id |= ch; + state = sw_error; + break; + + case sw_error: + ctx->error = ch << 24; + state = sw_error_2; + break; + + case sw_error_2: + ctx->error |= ch << 16; + state = sw_error_3; + break; + + case sw_error_3: + ctx->error |= ch << 8; + state = sw_error_4; + break; + + case sw_error_4: + ctx->error |= ch; + state = sw_debug; + break; + + case sw_debug: + break; + } + } + + ctx->rest -= p - b->pos; + ctx->frame_state = state; + b->pos = p; + + if (ctx->rest > 0) { + return NGX_AGAIN; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc goaway: %ui, stream %ui", + ctx->error, ctx->stream_id); + + ctx->state = ngx_http_grpc_st_start; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_grpc_parse_window_update(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b) +{ + u_char ch, *p, *last; + enum { + sw_start = 0, + sw_size_2, + sw_size_3, + sw_size_4 + } state; + + if (b->last - b->pos < (ssize_t) ctx->rest) { + last = b->last; + + } else { + last = b->pos + ctx->rest; + } + + state = ctx->frame_state; + + if (state == sw_start) { + if (ctx->rest != 4) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent window update frame " + "with invalid length: %uz", + ctx->rest); + return NGX_ERROR; + } + } + + for (p = b->pos; p < last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc window update byte: %02Xd s:%d", ch, state); +#endif + + switch (state) { + + case sw_start: + ctx->window_update = (ch & 0x7f) << 24; + state = sw_size_2; + break; + + case sw_size_2: + ctx->window_update |= ch << 16; + state = sw_size_3; + break; + + case sw_size_3: + ctx->window_update |= ch << 8; + state = sw_size_4; + break; + + case sw_size_4: + ctx->window_update |= ch; + state = sw_start; + break; + } + } + + ctx->rest -= p - b->pos; + ctx->frame_state = state; + b->pos = p; + + if (ctx->rest > 0) { + return NGX_AGAIN; + } + + ctx->state = ngx_http_grpc_st_start; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc window update: %ui", ctx->window_update); + + if (ctx->stream_id) { + + if (ctx->window_update > (size_t) NGX_HTTP_V2_MAX_WINDOW + - ctx->send_window) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large window update"); + return NGX_ERROR; + } + + ctx->send_window += ctx->window_update; + + } else { + + if (ctx->window_update > NGX_HTTP_V2_MAX_WINDOW + - ctx->connection->send_window) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large window update"); + return NGX_ERROR; + } + + ctx->connection->send_window += ctx->window_update; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_grpc_parse_settings(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, + ngx_buf_t *b) +{ + u_char ch, *p, *last; + ssize_t window_update; + enum { + sw_start = 0, + sw_id, + sw_id_2, + sw_value, + sw_value_2, + sw_value_3, + sw_value_4 + } state; + + if (b->last - b->pos < (ssize_t) ctx->rest) { + last = b->last; + + } else { + last = b->pos + ctx->rest; + } + + state = ctx->frame_state; + + if (state == sw_start) { + + if (ctx->stream_id) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent settings frame " + "with non-zero stream id: %ui", + ctx->stream_id); + return NGX_ERROR; + } + + if (ctx->flags & NGX_HTTP_V2_ACK_FLAG) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc settings ack"); + + if (ctx->rest != 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent settings frame " + "with ack flag and non-zero length: %uz", + ctx->rest); + return NGX_ERROR; + } + + ctx->state = ngx_http_grpc_st_start; + + return NGX_OK; + } + + if (ctx->rest % 6 != 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent settings frame " + "with invalid length: %uz", + ctx->rest); + return NGX_ERROR; + } + } + + for (p = b->pos; p < last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc settings byte: %02Xd s:%d", ch, state); +#endif + + switch (state) { + + case sw_start: + case sw_id: + ctx->setting_id = ch << 8; + state = sw_id_2; + break; + + case sw_id_2: + ctx->setting_id |= ch; + state = sw_value; + break; + + case sw_value: + ctx->setting_value = ch << 24; + state = sw_value_2; + break; + + case sw_value_2: + ctx->setting_value |= ch << 16; + state = sw_value_3; + break; + + case sw_value_3: + ctx->setting_value |= ch << 8; + state = sw_value_4; + break; + + case sw_value_4: + ctx->setting_value |= ch; + state = sw_id; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc setting: %ui %ui", + ctx->setting_id, ctx->setting_value); + + /* + * The following settings are defined by the protocol: + * + * SETTINGS_HEADER_TABLE_SIZE, SETTINGS_ENABLE_PUSH, + * SETTINGS_MAX_CONCURRENT_STREAMS, SETTINGS_INITIAL_WINDOW_SIZE, + * SETTINGS_MAX_FRAME_SIZE, SETTINGS_MAX_HEADER_LIST_SIZE + * + * Only SETTINGS_INITIAL_WINDOW_SIZE seems to be needed in + * a simple client. + */ + + if (ctx->setting_id == 0x04) { + /* SETTINGS_INITIAL_WINDOW_SIZE */ + + if (ctx->setting_value > NGX_HTTP_V2_MAX_WINDOW) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent settings frame " + "with too large initial window size: %ui", + ctx->setting_value); + return NGX_ERROR; + } + + window_update = ctx->setting_value + - ctx->connection->init_window; + ctx->connection->init_window = ctx->setting_value; + + if (ctx->send_window > 0 + && window_update > (ssize_t) NGX_HTTP_V2_MAX_WINDOW + - ctx->send_window) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent settings frame " + "with too large initial window size: %ui", + ctx->setting_value); + return NGX_ERROR; + } + + ctx->send_window += window_update; + } + + break; + } + } + + ctx->rest -= p - b->pos; + ctx->frame_state = state; + b->pos = p; + + if (ctx->rest > 0) { + return NGX_AGAIN; + } + + ctx->state = ngx_http_grpc_st_start; + + return ngx_http_grpc_send_settings_ack(r, ctx); +} + + +static ngx_int_t +ngx_http_grpc_parse_ping(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx, ngx_buf_t *b) +{ + u_char ch, *p, *last; + enum { + sw_start = 0, + sw_data_2, + sw_data_3, + sw_data_4, + sw_data_5, + sw_data_6, + sw_data_7, + sw_data_8 + } state; + + if (b->last - b->pos < (ssize_t) ctx->rest) { + last = b->last; + + } else { + last = b->pos + ctx->rest; + } + + state = ctx->frame_state; + + if (state == sw_start) { + + if (ctx->stream_id) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent ping frame " + "with non-zero stream id: %ui", + ctx->stream_id); + return NGX_ERROR; + } + + if (ctx->rest != 8) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent ping frame " + "with invalid length: %uz", + ctx->rest); + return NGX_ERROR; + } + + if (ctx->flags & NGX_HTTP_V2_ACK_FLAG) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent ping frame with ack flag"); + return NGX_ERROR; + } + } + + for (p = b->pos; p < last; p++) { + ch = *p; + +#if 0 + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc ping byte: %02Xd s:%d", ch, state); +#endif + + if (state < sw_data_8) { + ctx->ping_data[state] = ch; + state++; + + } else { + ctx->ping_data[7] = ch; + state = sw_start; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc ping"); + } + } + + ctx->rest -= p - b->pos; + ctx->frame_state = state; + b->pos = p; + + if (ctx->rest > 0) { + return NGX_AGAIN; + } + + ctx->state = ngx_http_grpc_st_start; + + return ngx_http_grpc_send_ping_ack(r, ctx); +} + + +static ngx_int_t +ngx_http_grpc_send_settings_ack(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx) +{ + ngx_chain_t *cl, **ll; + ngx_http_grpc_frame_t *f; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc send settings ack"); + + for (cl = ctx->out, ll = &ctx->out; cl; cl = cl->next) { + ll = &cl->next; + } + + cl = ngx_http_grpc_get_buf(r, ctx); + if (cl == NULL) { + return NGX_ERROR; + } + + f = (ngx_http_grpc_frame_t *) cl->buf->last; + cl->buf->last += sizeof(ngx_http_grpc_frame_t); + + f->length_0 = 0; + f->length_1 = 0; + f->length_2 = 0; + f->type = NGX_HTTP_V2_SETTINGS_FRAME; + f->flags = NGX_HTTP_V2_ACK_FLAG; + f->stream_id_0 = 0; + f->stream_id_1 = 0; + f->stream_id_2 = 0; + f->stream_id_3 = 0; + + *ll = cl; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_grpc_send_ping_ack(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx) +{ + ngx_chain_t *cl, **ll; + ngx_http_grpc_frame_t *f; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc send ping ack"); + + for (cl = ctx->out, ll = &ctx->out; cl; cl = cl->next) { + ll = &cl->next; + } + + cl = ngx_http_grpc_get_buf(r, ctx); + if (cl == NULL) { + return NGX_ERROR; + } + + f = (ngx_http_grpc_frame_t *) cl->buf->last; + cl->buf->last += sizeof(ngx_http_grpc_frame_t); + + f->length_0 = 0; + f->length_1 = 0; + f->length_2 = 8; + f->type = NGX_HTTP_V2_PING_FRAME; + f->flags = NGX_HTTP_V2_ACK_FLAG; + f->stream_id_0 = 0; + f->stream_id_1 = 0; + f->stream_id_2 = 0; + f->stream_id_3 = 0; + + cl->buf->last = ngx_copy(cl->buf->last, ctx->ping_data, 8); + + *ll = cl; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_grpc_send_window_update(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx) +{ + size_t n; + ngx_chain_t *cl, **ll; + ngx_http_grpc_frame_t *f; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc send window update: %uz %uz", + ctx->connection->recv_window, ctx->recv_window); + + for (cl = ctx->out, ll = &ctx->out; cl; cl = cl->next) { + ll = &cl->next; + } + + cl = ngx_http_grpc_get_buf(r, ctx); + if (cl == NULL) { + return NGX_ERROR; + } + + f = (ngx_http_grpc_frame_t *) cl->buf->last; + cl->buf->last += sizeof(ngx_http_grpc_frame_t); + + f->length_0 = 0; + f->length_1 = 0; + f->length_2 = 4; + f->type = NGX_HTTP_V2_WINDOW_UPDATE_FRAME; + f->flags = 0; + f->stream_id_0 = 0; + f->stream_id_1 = 0; + f->stream_id_2 = 0; + f->stream_id_3 = 0; + + n = NGX_HTTP_V2_MAX_WINDOW - ctx->connection->recv_window; + ctx->connection->recv_window = NGX_HTTP_V2_MAX_WINDOW; + + *cl->buf->last++ = (u_char) ((n >> 24) & 0xff); + *cl->buf->last++ = (u_char) ((n >> 16) & 0xff); + *cl->buf->last++ = (u_char) ((n >> 8) & 0xff); + *cl->buf->last++ = (u_char) (n & 0xff); + + f = (ngx_http_grpc_frame_t *) cl->buf->last; + cl->buf->last += sizeof(ngx_http_grpc_frame_t); + + f->length_0 = 0; + f->length_1 = 0; + f->length_2 = 4; + f->type = NGX_HTTP_V2_WINDOW_UPDATE_FRAME; + f->flags = 0; + f->stream_id_0 = (u_char) ((ctx->id >> 24) & 0xff); + f->stream_id_1 = (u_char) ((ctx->id >> 16) & 0xff); + f->stream_id_2 = (u_char) ((ctx->id >> 8) & 0xff); + f->stream_id_3 = (u_char) (ctx->id & 0xff); + + n = NGX_HTTP_V2_MAX_WINDOW - ctx->recv_window; + ctx->recv_window = NGX_HTTP_V2_MAX_WINDOW; + + *cl->buf->last++ = (u_char) ((n >> 24) & 0xff); + *cl->buf->last++ = (u_char) ((n >> 16) & 0xff); + *cl->buf->last++ = (u_char) ((n >> 8) & 0xff); + *cl->buf->last++ = (u_char) (n & 0xff); + + *ll = cl; + + return NGX_OK; +} + + +static ngx_chain_t * +ngx_http_grpc_get_buf(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx) +{ + ngx_buf_t *b; + ngx_chain_t *cl; + + 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_grpc_body_output_filter; + b->temporary = 1; + b->flush = 1; + + if (b->start == NULL) { + + /* + * each buffer is large enough to hold two window update + * frames in a row + */ + + b->start = ngx_palloc(r->pool, 2 * sizeof(ngx_http_grpc_frame_t) + 8); + if (b->start == NULL) { + return NULL; + } + + b->pos = b->start; + b->last = b->start; + + b->end = b->start + 2 * sizeof(ngx_http_grpc_frame_t) + 8; + } + + return cl; +} + + +static ngx_http_grpc_ctx_t * +ngx_http_grpc_get_ctx(ngx_http_request_t *r) +{ + ngx_http_grpc_ctx_t *ctx; + ngx_http_upstream_t *u; + + ctx = ngx_http_get_module_ctx(r, ngx_http_grpc_module); + + if (ctx->connection == NULL) { + u = r->upstream; + + if (ngx_http_grpc_get_connection_data(r, ctx, &u->peer) != NGX_OK) { + return NULL; + } + } + + return ctx; +} + + +static ngx_int_t +ngx_http_grpc_get_connection_data(ngx_http_request_t *r, + ngx_http_grpc_ctx_t *ctx, ngx_peer_connection_t *pc) +{ + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + + c = pc->connection; + + if (pc->cached) { + + /* + * for cached connections, connection data can be found + * in the cleanup handler + */ + + for (cln = c->pool->cleanup; cln; cln = cln->next) { + if (cln->handler == ngx_http_grpc_cleanup) { + ctx->connection = cln->data; + break; + } + } + + if (ctx->connection == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no connection data found for " + "keepalive http2 connection"); + return NGX_ERROR; + } + + ctx->send_window = ctx->connection->init_window; + ctx->recv_window = NGX_HTTP_V2_MAX_WINDOW; + + ctx->connection->last_stream_id += 2; + ctx->id = ctx->connection->last_stream_id; + + return NGX_OK; + } + + cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_grpc_conn_t)); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_grpc_cleanup; + ctx->connection = cln->data; + + ctx->connection->init_window = NGX_HTTP_V2_DEFAULT_WINDOW; + ctx->connection->send_window = NGX_HTTP_V2_DEFAULT_WINDOW; + ctx->connection->recv_window = NGX_HTTP_V2_MAX_WINDOW; + + ctx->send_window = NGX_HTTP_V2_DEFAULT_WINDOW; + ctx->recv_window = NGX_HTTP_V2_MAX_WINDOW; + + ctx->id = 1; + ctx->connection->last_stream_id = 1; + + return NGX_OK; +} + + +static void +ngx_http_grpc_cleanup(void *data) +{ +#if 0 + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "grpc cleanup"); +#endif + return; +} + + +static void +ngx_http_grpc_abort_request(ngx_http_request_t *r) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "abort grpc request"); + return; +} + + +static void +ngx_http_grpc_finalize_request(ngx_http_request_t *r, ngx_int_t rc) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "finalize grpc request"); + return; +} + + +static ngx_int_t +ngx_http_grpc_internal_trailers_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_table_elt_t *te; + + te = r->headers_in.te; + + if (te == NULL) { + v->not_found = 1; + return NGX_OK; + } + + if (ngx_strlcasestrn(te->value.data, te->value.data + te->value.len, + (u_char *) "trailers", 8 - 1) + == NULL) + { + v->not_found = 1; + return NGX_OK; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + + v->data = (u_char *) "trailers"; + v->len = sizeof("trailers") - 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_grpc_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_grpc_vars; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static void * +ngx_http_grpc_create_loc_conf(ngx_conf_t *cf) +{ + ngx_http_grpc_loc_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_grpc_loc_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->upstream.ignore_headers = 0; + * conf->upstream.next_upstream = 0; + * conf->upstream.hide_headers_hash = { NULL, 0 }; + * conf->upstream.ssl_name = NULL; + * + * conf->headers_source = NULL; + * conf->headers.lengths = NULL; + * conf->headers.values = NULL; + * conf->headers.hash = { NULL, 0 }; + * conf->host = { 0, NULL }; + * conf->host_set = 0; + * conf->ssl = 0; + * conf->ssl_protocols = 0; + * conf->ssl_ciphers = { 0, NULL }; + * conf->ssl_trusted_certificate = { 0, NULL }; + * conf->ssl_crl = { 0, NULL }; + * conf->ssl_certificate = { 0, NULL }; + * conf->ssl_certificate_key = { 0, NULL }; + */ + + conf->upstream.local = NGX_CONF_UNSET_PTR; + conf->upstream.next_upstream_tries = NGX_CONF_UNSET_UINT; + conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC; + conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC; + conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC; + conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC; + + conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE; + + conf->upstream.hide_headers = NGX_CONF_UNSET_PTR; + conf->upstream.pass_headers = NGX_CONF_UNSET_PTR; + + conf->upstream.intercept_errors = NGX_CONF_UNSET; + +#if (NGX_HTTP_SSL) + conf->upstream.ssl_session_reuse = NGX_CONF_UNSET; + conf->upstream.ssl_server_name = NGX_CONF_UNSET; + conf->upstream.ssl_verify = NGX_CONF_UNSET; + conf->ssl_verify_depth = NGX_CONF_UNSET_UINT; + conf->ssl_passwords = NGX_CONF_UNSET_PTR; +#endif + + /* the hardcoded values */ + conf->upstream.cyclic_temp_file = 0; + conf->upstream.buffering = 0; + conf->upstream.ignore_client_abort = 0; + conf->upstream.send_lowat = 0; + conf->upstream.bufs.num = 0; + conf->upstream.busy_buffers_size = 0; + conf->upstream.max_temp_file_size = 0; + conf->upstream.temp_file_write_size = 0; + conf->upstream.pass_request_headers = 1; + conf->upstream.pass_request_body = 1; + conf->upstream.force_ranges = 0; + conf->upstream.pass_trailers = 1; + conf->upstream.preserve_output = 1; + + ngx_str_set(&conf->upstream.module, "grpc"); + + return conf; +} + + +static char * +ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_grpc_loc_conf_t *prev = parent; + ngx_http_grpc_loc_conf_t *conf = child; + + ngx_int_t rc; + ngx_hash_init_t hash; + ngx_http_core_loc_conf_t *clcf; + + ngx_conf_merge_ptr_value(conf->upstream.local, + prev->upstream.local, NULL); + + ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries, + prev->upstream.next_upstream_tries, 0); + + ngx_conf_merge_msec_value(conf->upstream.connect_timeout, + prev->upstream.connect_timeout, 60000); + + ngx_conf_merge_msec_value(conf->upstream.send_timeout, + prev->upstream.send_timeout, 60000); + + ngx_conf_merge_msec_value(conf->upstream.read_timeout, + prev->upstream.read_timeout, 60000); + + ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout, + prev->upstream.next_upstream_timeout, 0); + + ngx_conf_merge_size_value(conf->upstream.buffer_size, + prev->upstream.buffer_size, + (size_t) ngx_pagesize); + + ngx_conf_merge_bitmask_value(conf->upstream.ignore_headers, + prev->upstream.ignore_headers, + NGX_CONF_BITMASK_SET); + + ngx_conf_merge_bitmask_value(conf->upstream.next_upstream, + prev->upstream.next_upstream, + (NGX_CONF_BITMASK_SET + |NGX_HTTP_UPSTREAM_FT_ERROR + |NGX_HTTP_UPSTREAM_FT_TIMEOUT)); + + if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) { + conf->upstream.next_upstream = NGX_CONF_BITMASK_SET + |NGX_HTTP_UPSTREAM_FT_OFF; + } + + ngx_conf_merge_value(conf->upstream.intercept_errors, + prev->upstream.intercept_errors, 0); + +#if (NGX_HTTP_SSL) + + ngx_conf_merge_value(conf->upstream.ssl_session_reuse, + prev->upstream.ssl_session_reuse, 1); + + ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols, + (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 + |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); + + ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers, + "DEFAULT"); + + if (conf->upstream.ssl_name == NULL) { + conf->upstream.ssl_name = prev->upstream.ssl_name; + } + + ngx_conf_merge_value(conf->upstream.ssl_server_name, + prev->upstream.ssl_server_name, 0); + ngx_conf_merge_value(conf->upstream.ssl_verify, + prev->upstream.ssl_verify, 0); + ngx_conf_merge_uint_value(conf->ssl_verify_depth, + prev->ssl_verify_depth, 1); + ngx_conf_merge_str_value(conf->ssl_trusted_certificate, + prev->ssl_trusted_certificate, ""); + ngx_conf_merge_str_value(conf->ssl_crl, prev->ssl_crl, ""); + + ngx_conf_merge_str_value(conf->ssl_certificate, + prev->ssl_certificate, ""); + ngx_conf_merge_str_value(conf->ssl_certificate_key, + prev->ssl_certificate_key, ""); + ngx_conf_merge_ptr_value(conf->ssl_passwords, prev->ssl_passwords, NULL); + + if (conf->ssl && ngx_http_grpc_set_ssl(cf, conf) != NGX_OK) { + return NGX_CONF_ERROR; + } + +#endif + + hash.max_size = 512; + hash.bucket_size = ngx_align(64, ngx_cacheline_size); + hash.name = "grpc_headers_hash"; + + if (ngx_http_upstream_hide_headers_hash(cf, &conf->upstream, + &prev->upstream, ngx_http_grpc_hide_headers, &hash) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + + if (clcf->noname && conf->upstream.upstream == NULL) { + conf->upstream.upstream = prev->upstream.upstream; + conf->host = prev->host; +#if (NGX_HTTP_SSL) + conf->upstream.ssl = prev->upstream.ssl; +#endif + } + + if (clcf->lmt_excpt && clcf->handler == NULL && conf->upstream.upstream) { + clcf->handler = ngx_http_grpc_handler; + } + + if (conf->headers_source == NULL) { + conf->headers = prev->headers; + conf->headers_source = prev->headers_source; + conf->host_set = prev->host_set; + } + + rc = ngx_http_grpc_init_headers(cf, conf, &conf->headers, + ngx_http_grpc_headers); + if (rc != NGX_OK) { + return NGX_CONF_ERROR; + } + + /* + * special handling to preserve conf->headers in the "http" section + * to inherit it to all servers + */ + + if (prev->headers.hash.buckets == NULL + && conf->headers_source == prev->headers_source) + { + prev->headers = conf->headers; + prev->host_set = conf->host_set; + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_http_grpc_init_headers(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *conf, + ngx_http_grpc_headers_t *headers, ngx_keyval_t *default_headers) +{ + u_char *p; + size_t size; + uintptr_t *code; + ngx_uint_t i; + ngx_array_t headers_names, headers_merged; + ngx_keyval_t *src, *s, *h; + ngx_hash_key_t *hk; + ngx_hash_init_t hash; + ngx_http_script_compile_t sc; + ngx_http_script_copy_code_t *copy; + + if (headers->hash.buckets) { + return NGX_OK; + } + + if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_array_init(&headers_merged, cf->temp_pool, 4, sizeof(ngx_keyval_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + headers->lengths = ngx_array_create(cf->pool, 64, 1); + if (headers->lengths == NULL) { + return NGX_ERROR; + } + + headers->values = ngx_array_create(cf->pool, 512, 1); + if (headers->values == NULL) { + return NGX_ERROR; + } + + if (conf->headers_source) { + + src = conf->headers_source->elts; + for (i = 0; i < conf->headers_source->nelts; i++) { + + if (src[i].key.len == 4 + && ngx_strncasecmp(src[i].key.data, (u_char *) "Host", 4) == 0) + { + conf->host_set = 1; + } + + s = ngx_array_push(&headers_merged); + if (s == NULL) { + return NGX_ERROR; + } + + *s = src[i]; + } + } + + h = default_headers; + + while (h->key.len) { + + src = headers_merged.elts; + for (i = 0; i < headers_merged.nelts; i++) { + if (ngx_strcasecmp(h->key.data, src[i].key.data) == 0) { + goto next; + } + } + + s = ngx_array_push(&headers_merged); + if (s == NULL) { + return NGX_ERROR; + } + + *s = *h; + + next: + + h++; + } + + + src = headers_merged.elts; + for (i = 0; i < headers_merged.nelts; i++) { + + hk = ngx_array_push(&headers_names); + if (hk == NULL) { + return NGX_ERROR; + } + + hk->key = src[i].key; + hk->key_hash = ngx_hash_key_lc(src[i].key.data, src[i].key.len); + hk->value = (void *) 1; + + if (src[i].value.len == 0) { + continue; + } + + copy = ngx_array_push_n(headers->lengths, + sizeof(ngx_http_script_copy_code_t)); + if (copy == NULL) { + return NGX_ERROR; + } + + copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code; + copy->len = src[i].key.len; + + size = (sizeof(ngx_http_script_copy_code_t) + + src[i].key.len + sizeof(uintptr_t) - 1) + & ~(sizeof(uintptr_t) - 1); + + copy = ngx_array_push_n(headers->values, size); + if (copy == NULL) { + return NGX_ERROR; + } + + copy->code = ngx_http_script_copy_code; + copy->len = src[i].key.len; + + p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t); + ngx_memcpy(p, src[i].key.data, src[i].key.len); + + ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); + + sc.cf = cf; + sc.source = &src[i].value; + sc.flushes = &headers->flushes; + sc.lengths = &headers->lengths; + sc.values = &headers->values; + + if (ngx_http_script_compile(&sc) != NGX_OK) { + return NGX_ERROR; + } + + code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t)); + if (code == NULL) { + return NGX_ERROR; + } + + *code = (uintptr_t) NULL; + + code = ngx_array_push_n(headers->values, sizeof(uintptr_t)); + if (code == NULL) { + return NGX_ERROR; + } + + *code = (uintptr_t) NULL; + } + + code = ngx_array_push_n(headers->lengths, sizeof(uintptr_t)); + if (code == NULL) { + return NGX_ERROR; + } + + *code = (uintptr_t) NULL; + + + hash.hash = &headers->hash; + hash.key = ngx_hash_key_lc; + hash.max_size = 512; + hash.bucket_size = 64; + hash.name = "grpc_headers_hash"; + hash.pool = cf->pool; + hash.temp_pool = NULL; + + return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts); +} + + +static char * +ngx_http_grpc_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_grpc_loc_conf_t *glcf = conf; + + size_t add; + ngx_str_t *value, *url; + ngx_url_t u; + ngx_http_core_loc_conf_t *clcf; + + if (glcf->upstream.upstream) { + return "is duplicate"; + } + + value = cf->args->elts; + url = &value[1]; + + if (ngx_strncasecmp(url->data, (u_char *) "grpc://", 7) == 0) { + add = 7; + + } else if (ngx_strncasecmp(url->data, (u_char *) "grpcs://", 8) == 0) { + +#if (NGX_HTTP_SSL) + glcf->ssl = 1; + + add = 8; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "grpcs protocol requires SSL support"); + return NGX_CONF_ERROR; +#endif + + } else { + add = 0; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url.len = url->len - add; + u.url.data = url->data + add; + u.no_resolve = 1; + + glcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0); + if (glcf->upstream.upstream == NULL) { + return NGX_CONF_ERROR; + } + + if (u.family != AF_UNIX) { + + if (u.no_port) { + glcf->host = u.host; + + } else { + glcf->host.len = u.host.len + 1 + u.port_text.len; + glcf->host.data = u.host.data; + } + + } else { + ngx_str_set(&glcf->host, "localhost"); + } + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + + clcf->handler = ngx_http_grpc_handler; + + if (clcf->name.data[clcf->name.len - 1] == '/') { + clcf->auto_redirect = 1; + } + + return NGX_CONF_OK; +} + + +#if (NGX_HTTP_SSL) + +static char * +ngx_http_grpc_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_grpc_loc_conf_t *glcf = conf; + + ngx_str_t *value; + + if (glcf->ssl_passwords != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + glcf->ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]); + + if (glcf->ssl_passwords == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_http_grpc_set_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *glcf) +{ + ngx_pool_cleanup_t *cln; + + glcf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); + if (glcf->upstream.ssl == NULL) { + return NGX_ERROR; + } + + glcf->upstream.ssl->log = cf->log; + + if (ngx_ssl_create(glcf->upstream.ssl, glcf->ssl_protocols, NULL) + != NGX_OK) + { + return NGX_ERROR; + } + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_ssl_cleanup_ctx; + cln->data = glcf->upstream.ssl; + + if (glcf->ssl_certificate.len) { + + if (glcf->ssl_certificate_key.len == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"grpc_ssl_certificate_key\" is defined " + "for certificate \"%V\"", &glcf->ssl_certificate); + return NGX_ERROR; + } + + if (ngx_ssl_certificate(cf, glcf->upstream.ssl, &glcf->ssl_certificate, + &glcf->ssl_certificate_key, glcf->ssl_passwords) + != NGX_OK) + { + return NGX_ERROR; + } + } + + if (ngx_ssl_ciphers(cf, glcf->upstream.ssl, &glcf->ssl_ciphers, 0) + != NGX_OK) + { + return NGX_ERROR; + } + + if (glcf->upstream.ssl_verify) { + if (glcf->ssl_trusted_certificate.len == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no grpc_ssl_trusted_certificate for grpc_ssl_verify"); + return NGX_ERROR; + } + + if (ngx_ssl_trusted_certificate(cf, glcf->upstream.ssl, + &glcf->ssl_trusted_certificate, + glcf->ssl_verify_depth) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_ssl_crl(cf, glcf->upstream.ssl, &glcf->ssl_crl) != NGX_OK) { + return NGX_ERROR; + } + } + +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + + if (SSL_CTX_set_alpn_protos(glcf->upstream.ssl->ctx, + (u_char *) "\x02h2", 3) + != 0) + { + ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, + "SSL_CTX_set_alpn_protos() failed"); + return NGX_ERROR; + } + +#endif + + return NGX_OK; +} + +#endif
diff --git a/src/http/modules/ngx_http_log_module.c b/src/http/modules/ngx_http_log_module.c index 917ed55..f7c4bd2 100644 --- a/src/http/modules/ngx_http_log_module.c +++ b/src/http/modules/ngx_http_log_module.c
@@ -90,6 +90,11 @@ } ngx_http_log_var_t; +#define NGX_HTTP_LOG_ESCAPE_DEFAULT 0 +#define NGX_HTTP_LOG_ESCAPE_JSON 1 +#define NGX_HTTP_LOG_ESCAPE_NONE 2 + + static void ngx_http_log_write(ngx_http_request_t *r, ngx_http_log_t *log, u_char *buf, size_t len); static ssize_t ngx_http_log_script_write(ngx_http_request_t *r, @@ -126,7 +131,7 @@ ngx_http_log_op_t *op); static ngx_int_t ngx_http_log_variable_compile(ngx_conf_t *cf, - ngx_http_log_op_t *op, ngx_str_t *value, ngx_uint_t json); + ngx_http_log_op_t *op, ngx_str_t *value, ngx_uint_t escape); static size_t ngx_http_log_variable_getlen(ngx_http_request_t *r, uintptr_t data); static u_char *ngx_http_log_variable(ngx_http_request_t *r, u_char *buf, @@ -136,6 +141,10 @@ uintptr_t data); static u_char *ngx_http_log_json_variable(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op); +static size_t ngx_http_log_unescaped_variable_getlen(ngx_http_request_t *r, + uintptr_t data); +static u_char *ngx_http_log_unescaped_variable(ngx_http_request_t *r, + u_char *buf, ngx_http_log_op_t *op); static void *ngx_http_log_create_main_conf(ngx_conf_t *cf); @@ -905,7 +914,7 @@ static ngx_int_t ngx_http_log_variable_compile(ngx_conf_t *cf, ngx_http_log_op_t *op, - ngx_str_t *value, ngx_uint_t json) + ngx_str_t *value, ngx_uint_t escape) { ngx_int_t index; @@ -916,11 +925,18 @@ op->len = 0; - if (json) { + switch (escape) { + case NGX_HTTP_LOG_ESCAPE_JSON: op->getlen = ngx_http_log_json_variable_getlen; op->run = ngx_http_log_json_variable; + break; - } else { + case NGX_HTTP_LOG_ESCAPE_NONE: + op->getlen = ngx_http_log_unescaped_variable_getlen; + op->run = ngx_http_log_unescaped_variable; + break; + + default: /* NGX_HTTP_LOG_ESCAPE_DEFAULT */ op->getlen = ngx_http_log_variable_getlen; op->run = ngx_http_log_variable; } @@ -1073,6 +1089,39 @@ } +static size_t +ngx_http_log_unescaped_variable_getlen(ngx_http_request_t *r, uintptr_t data) +{ + ngx_http_variable_value_t *value; + + value = ngx_http_get_indexed_variable(r, data); + + if (value == NULL || value->not_found) { + return 0; + } + + value->escape = 0; + + return value->len; +} + + +static u_char * +ngx_http_log_unescaped_variable(ngx_http_request_t *r, u_char *buf, + ngx_http_log_op_t *op) +{ + ngx_http_variable_value_t *value; + + value = ngx_http_get_indexed_variable(r, op->data); + + if (value == NULL || value->not_found) { + return buf; + } + + return ngx_cpymem(buf, value->data, value->len); +} + + static void * ngx_http_log_create_main_conf(ngx_conf_t *cf) { @@ -1536,18 +1585,21 @@ size_t i, len; ngx_str_t *value, var; ngx_int_t *flush; - ngx_uint_t bracket, json; + ngx_uint_t bracket, escape; ngx_http_log_op_t *op; ngx_http_log_var_t *v; - json = 0; + escape = NGX_HTTP_LOG_ESCAPE_DEFAULT; value = args->elts; if (s < args->nelts && ngx_strncmp(value[s].data, "escape=", 7) == 0) { data = value[s].data + 7; if (ngx_strcmp(data, "json") == 0) { - json = 1; + escape = NGX_HTTP_LOG_ESCAPE_JSON; + + } else if (ngx_strcmp(data, "none") == 0) { + escape = NGX_HTTP_LOG_ESCAPE_NONE; } else if (ngx_strcmp(data, "default") != 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, @@ -1636,7 +1688,7 @@ } } - if (ngx_http_log_variable_compile(cf, op, &var, json) + if (ngx_http_log_variable_compile(cf, op, &var, escape) != NGX_OK) { return NGX_CONF_ERROR;
diff --git a/src/http/modules/ngx_http_memcached_module.c b/src/http/modules/ngx_http_memcached_module.c index b681e45..82fa713 100644 --- a/src/http/modules/ngx_http_memcached_module.c +++ b/src/http/modules/ngx_http_memcached_module.c
@@ -592,8 +592,6 @@ * conf->upstream.bufs.num = 0; * conf->upstream.next_upstream = 0; * conf->upstream.temp_path = NULL; - * conf->upstream.uri = { 0, NULL }; - * conf->upstream.location = NULL; */ conf->upstream.local = NGX_CONF_UNSET_PTR; @@ -619,7 +617,6 @@ conf->upstream.pass_request_headers = 0; conf->upstream.pass_request_body = 0; conf->upstream.force_ranges = 1; - conf->upstream.pass_trailers = 0; conf->index = NGX_CONF_UNSET; conf->gzip_flag = NGX_CONF_UNSET_UINT;
diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index 655355e..8c2f8cb 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c
@@ -2328,36 +2328,6 @@ return NGX_ERROR; } - /* provide continuous buffer for subrequests in memory */ - - if (r->subrequest_in_memory) { - - cl = u->out_bufs; - - if (cl) { - buf->pos = cl->buf->pos; - } - - buf->last = buf->pos; - - for (cl = u->out_bufs; cl; cl = cl->next) { - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http proxy in memory %p-%p %O", - cl->buf->pos, cl->buf->last, ngx_buf_size(cl->buf)); - - if (buf->last == cl->buf->pos) { - buf->last = cl->buf->last; - continue; - } - - buf->last = ngx_movemem(buf->last, cl->buf->pos, - cl->buf->last - cl->buf->pos); - - cl->buf->pos = buf->last - (cl->buf->last - cl->buf->pos); - cl->buf->last = buf->last; - } - } - return NGX_OK; } @@ -2834,13 +2804,13 @@ * conf->upstream.cache_methods = 0; * conf->upstream.temp_path = NULL; * conf->upstream.hide_headers_hash = { NULL, 0 }; - * conf->upstream.uri = { 0, NULL }; - * conf->upstream.location = NULL; * conf->upstream.store_lengths = NULL; * conf->upstream.store_values = NULL; * conf->upstream.ssl_name = NULL; * * conf->method = NULL; + * conf->location = NULL; + * conf->url = { 0, NULL }; * conf->headers_source = NULL; * conf->headers.lengths = NULL; * conf->headers.values = NULL; @@ -2916,12 +2886,11 @@ conf->ssl_passwords = NGX_CONF_UNSET_PTR; #endif - /* the hardcoded values */ + /* "proxy_cyclic_temp_file" is disabled */ conf->upstream.cyclic_temp_file = 0; - conf->upstream.change_buffering = 1; - conf->upstream.pass_trailers = 0; conf->redirect = NGX_CONF_UNSET; + conf->upstream.change_buffering = 1; conf->cookie_domains = NGX_CONF_UNSET_PTR; conf->cookie_paths = NGX_CONF_UNSET_PTR;
diff --git a/src/http/modules/ngx_http_scgi_module.c b/src/http/modules/ngx_http_scgi_module.c index f217061..3fb227b 100644 --- a/src/http/modules/ngx_http_scgi_module.c +++ b/src/http/modules/ngx_http_scgi_module.c
@@ -1236,10 +1236,10 @@ conf->upstream.intercept_errors = NGX_CONF_UNSET; - /* the hardcoded values */ + /* "scgi_cyclic_temp_file" is disabled */ conf->upstream.cyclic_temp_file = 0; + conf->upstream.change_buffering = 1; - conf->upstream.pass_trailers = 0; ngx_str_set(&conf->upstream.module, "scgi");
diff --git a/src/http/modules/ngx_http_ssi_filter_module.c b/src/http/modules/ngx_http_ssi_filter_module.c index c05a5d6..a6f45b4 100644 --- a/src/http/modules/ngx_http_ssi_filter_module.c +++ b/src/http/modules/ngx_http_ssi_filter_module.c
@@ -2232,9 +2232,11 @@ { ngx_str_t *value = data; - if (r->upstream) { - value->len = r->upstream->buffer.last - r->upstream->buffer.pos; - value->data = r->upstream->buffer.pos; + if (r->headers_out.status < NGX_HTTP_SPECIAL_RESPONSE + && r->out && r->out->buf) + { + value->len = r->out->buf->last - r->out->buf->pos; + value->data = r->out->buf->pos; } return rc;
diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c index 472707b..124da4d 100644 --- a/src/http/modules/ngx_http_uwsgi_module.c +++ b/src/http/modules/ngx_http_uwsgi_module.c
@@ -1451,10 +1451,10 @@ conf->ssl_passwords = NGX_CONF_UNSET_PTR; #endif - /* the hardcoded values */ + /* "uwsgi_cyclic_temp_file" is disabled */ conf->upstream.cyclic_temp_file = 0; + conf->upstream.change_buffering = 1; - conf->upstream.pass_trailers = 0; ngx_str_set(&conf->upstream.module, "uwsgi");
diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 90c73a4..0153a15 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c
@@ -399,6 +399,13 @@ offsetof(ngx_http_core_loc_conf_t, sendfile_max_chunk), NULL }, + { ngx_string("subrequest_output_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_core_loc_conf_t, subrequest_output_buffer_size), + NULL }, + { ngx_string("aio"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_core_set_aio, @@ -2237,6 +2244,12 @@ return NGX_ERROR; } + if (r->subrequest_in_memory) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "nested in-memory subrequest \"%V\"", uri); + return NGX_ERROR; + } + sr = ngx_pcalloc(r->pool, sizeof(ngx_http_request_t)); if (sr == NULL) { return NGX_ERROR; @@ -2318,6 +2331,10 @@ sr->log_handler = r->log_handler; + if (sr->subrequest_in_memory) { + sr->filter_need_in_memory = 1; + } + if (!sr->background) { if (c->data == r && r->postponed == NULL) { c->data = sr; @@ -3356,6 +3373,7 @@ clcf->internal = NGX_CONF_UNSET; clcf->sendfile = NGX_CONF_UNSET; clcf->sendfile_max_chunk = NGX_CONF_UNSET_SIZE; + clcf->subrequest_output_buffer_size = NGX_CONF_UNSET_SIZE; clcf->aio = NGX_CONF_UNSET; clcf->aio_write = NGX_CONF_UNSET; #if (NGX_THREADS) @@ -3578,6 +3596,9 @@ ngx_conf_merge_value(conf->sendfile, prev->sendfile, 0); ngx_conf_merge_size_value(conf->sendfile_max_chunk, prev->sendfile_max_chunk, 0); + ngx_conf_merge_size_value(conf->subrequest_output_buffer_size, + prev->subrequest_output_buffer_size, + (size_t) ngx_pagesize); ngx_conf_merge_value(conf->aio, prev->aio, NGX_HTTP_AIO_OFF); ngx_conf_merge_value(conf->aio_write, prev->aio_write, 0); #if (NGX_THREADS)
diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index 97ba825..66c029f 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h
@@ -351,6 +351,8 @@ size_t limit_rate_after; /* limit_rate_after */ size_t sendfile_max_chunk; /* sendfile_max_chunk */ size_t read_ahead; /* read_ahead */ + size_t subrequest_output_buffer_size; + /* subrequest_output_buffer_size */ ngx_msec_t client_body_timeout; /* client_body_timeout */ ngx_msec_t send_timeout; /* send_timeout */
diff --git a/src/http/ngx_http_postpone_filter_module.c b/src/http/ngx_http_postpone_filter_module.c index 55f2698..599d263 100644 --- a/src/http/ngx_http_postpone_filter_module.c +++ b/src/http/ngx_http_postpone_filter_module.c
@@ -12,6 +12,8 @@ static ngx_int_t ngx_http_postpone_filter_add(ngx_http_request_t *r, ngx_chain_t *in); +static ngx_int_t ngx_http_postpone_filter_in_memory(ngx_http_request_t *r, + ngx_chain_t *in); static ngx_int_t ngx_http_postpone_filter_init(ngx_conf_t *cf); @@ -60,6 +62,10 @@ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, "http postpone filter \"%V?%V\" %p", &r->uri, &r->args, in); + if (r->subrequest_in_memory) { + return ngx_http_postpone_filter_in_memory(r, in); + } + if (r != c->data) { if (in) { @@ -172,6 +178,78 @@ static ngx_int_t +ngx_http_postpone_filter_in_memory(ngx_http_request_t *r, ngx_chain_t *in) +{ + size_t len; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; + + c = r->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http postpone filter in memory"); + + if (r->out == NULL) { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->headers_out.content_length_n != -1) { + len = r->headers_out.content_length_n; + + if (len > clcf->subrequest_output_buffer_size) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "too big subrequest response: %uz", len); + return NGX_ERROR; + } + + } else { + len = clcf->subrequest_output_buffer_size; + } + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last_buf = 1; + + r->out = ngx_alloc_chain_link(r->pool); + if (r->out == NULL) { + return NGX_ERROR; + } + + r->out->buf = b; + r->out->next = NULL; + } + + b = r->out->buf; + + for ( /* void */ ; in; in = in->next) { + + if (ngx_buf_special(in->buf)) { + continue; + } + + len = in->buf->last - in->buf->pos; + + if (len > (size_t) (b->end - b->last)) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "too big subrequest response"); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http postpone filter in memory %uz bytes", len); + + b->last = ngx_cpymem(b->last, in->buf->pos, len); + in->buf->pos = in->buf->last; + } + + return NGX_OK; +} + + +static ngx_int_t ngx_http_postpone_filter_init(ngx_conf_t *cf) { ngx_http_next_body_filter = ngx_http_top_body_filter;
diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index f68e938..b46922b 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c
@@ -132,6 +132,10 @@ offsetof(ngx_http_headers_in_t, transfer_encoding), ngx_http_process_header_line }, + { ngx_string("TE"), + offsetof(ngx_http_headers_in_t, te), + ngx_http_process_header_line }, + { ngx_string("Expect"), offsetof(ngx_http_headers_in_t, expect), ngx_http_process_unique_header_line },
diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 4352737..4f798d3 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h
@@ -197,6 +197,7 @@ ngx_table_elt_t *if_range; ngx_table_elt_t *transfer_encoding; + ngx_table_elt_t *te; ngx_table_elt_t *expect; ngx_table_elt_t *upgrade;
diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index 270bec1..76bd66d 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c
@@ -57,8 +57,6 @@ ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_process_trailers(ngx_http_request_t *r, ngx_http_upstream_t *u); -static void ngx_http_upstream_process_body_in_memory(ngx_http_request_t *r, - ngx_http_upstream_t *u); static void ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u); static void ngx_http_upstream_upgrade(ngx_http_request_t *r, @@ -151,8 +149,6 @@ ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_copy_allow_ranges(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); -static ngx_int_t ngx_http_upstream_copy_trailer(ngx_http_request_t *r, - ngx_table_elt_t *h, ngx_uint_t offset); #if (NGX_HTTP_GZIP || NGX_COMPAT) static ngx_int_t ngx_http_upstream_copy_content_encoding(ngx_http_request_t *r, @@ -316,10 +312,6 @@ ngx_http_upstream_process_charset, 0, ngx_http_upstream_copy_header_line, 0, 0 }, - { ngx_string("Trailer"), - ngx_http_upstream_ignore_header_line, 0, - ngx_http_upstream_copy_trailer, 0, 0 }, - { ngx_string("Transfer-Encoding"), ngx_http_upstream_process_transfer_encoding, 0, ngx_http_upstream_ignore_header_line, 0, 0 }, @@ -1628,6 +1620,7 @@ u->request_sent = 0; u->request_body_sent = 0; + u->request_body_blocked = 0; if (rc == NGX_AGAIN) { ngx_add_timer(c->write, u->conf->connect_timeout); @@ -2006,7 +1999,7 @@ } if (rc == NGX_AGAIN) { - if (!c->write->ready) { + if (!c->write->ready || u->request_body_blocked) { ngx_add_timer(c->write, u->conf->send_timeout); } else if (c->write->timer_set) { @@ -2031,7 +2024,7 @@ } if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) { - if (ngx_tcp_push(c->fd) == NGX_ERROR) { + if (ngx_tcp_push(c->fd) == -1) { ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno, ngx_tcp_push_n " failed"); ngx_http_upstream_finalize_request(r, u, @@ -2042,7 +2035,9 @@ c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; } - u->write_event_handler = ngx_http_upstream_dummy_handler; + if (!u->conf->preserve_output) { + u->write_event_handler = ngx_http_upstream_dummy_handler; + } if (ngx_handle_write_event(c->write, 0) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, @@ -2083,7 +2078,16 @@ out = NULL; } - return ngx_output_chain(&u->output, out); + rc = ngx_output_chain(&u->output, out); + + if (rc == NGX_AGAIN) { + u->request_body_blocked = 1; + + } else { + u->request_body_blocked = 0; + } + + return rc; } if (!u->request_sent) { @@ -2124,6 +2128,13 @@ ngx_free_chain(r->pool, ln); } + if (rc == NGX_AGAIN) { + u->request_body_blocked = 1; + + } else { + u->request_body_blocked = 0; + } + if (rc == NGX_OK && !r->reading_body) { break; } @@ -2188,7 +2199,7 @@ #endif - if (u->header_sent) { + if (u->header_sent && !u->conf->preserve_output) { u->write_event_handler = ngx_http_upstream_dummy_handler; (void) ngx_handle_write_event(c->write, 0); @@ -2379,45 +2390,7 @@ return; } - if (!r->subrequest_in_memory) { - ngx_http_upstream_send_response(r, u); - return; - } - - /* subrequest content in memory */ - - if (u->input_filter == NULL) { - u->input_filter_init = ngx_http_upstream_non_buffered_filter_init; - u->input_filter = ngx_http_upstream_non_buffered_filter; - u->input_filter_ctx = r; - } - - if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) { - ngx_http_upstream_finalize_request(r, u, NGX_ERROR); - return; - } - - n = u->buffer.last - u->buffer.pos; - - if (n) { - u->buffer.last = u->buffer.pos; - - u->state->response_length += n; - - if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { - ngx_http_upstream_finalize_request(r, u, NGX_ERROR); - return; - } - } - - if (u->length == 0) { - ngx_http_upstream_finalize_request(r, u, 0); - return; - } - - u->read_event_handler = ngx_http_upstream_process_body_in_memory; - - ngx_http_upstream_process_body_in_memory(r, u); + ngx_http_upstream_send_response(r, u); } @@ -2820,13 +2793,14 @@ static ngx_int_t -ngx_http_upstream_process_trailers(ngx_http_request_t *r, ngx_http_upstream_t *u) +ngx_http_upstream_process_trailers(ngx_http_request_t *r, + ngx_http_upstream_t *u) { - ngx_uint_t i; + ngx_uint_t i; ngx_list_part_t *part; ngx_table_elt_t *h, *ho; - if (!r->expect_trailers || !u->conf->pass_trailers) { + if (!u->conf->pass_trailers) { return NGX_OK; } @@ -2845,6 +2819,12 @@ i = 0; } + if (ngx_hash_find(&u->conf->hide_headers_hash, h[i].hash, + h[i].lowcase_key, h[i].key.len)) + { + continue; + } + ho = ngx_list_push(&r->headers_out.trailers); if (ho == NULL) { return NGX_ERROR; @@ -2858,84 +2838,6 @@ static void -ngx_http_upstream_process_body_in_memory(ngx_http_request_t *r, - ngx_http_upstream_t *u) -{ - size_t size; - ssize_t n; - ngx_buf_t *b; - ngx_event_t *rev; - ngx_connection_t *c; - - c = u->peer.connection; - rev = c->read; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http upstream process body in memory"); - - if (rev->timedout) { - ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out"); - ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT); - return; - } - - b = &u->buffer; - - for ( ;; ) { - - size = b->end - b->last; - - if (size == 0) { - ngx_log_error(NGX_LOG_ALERT, c->log, 0, - "upstream buffer is too small to read response"); - ngx_http_upstream_finalize_request(r, u, NGX_ERROR); - return; - } - - n = c->recv(c, b->last, size); - - if (n == NGX_AGAIN) { - break; - } - - if (n == 0 || n == NGX_ERROR) { - ngx_http_upstream_finalize_request(r, u, n); - return; - } - - u->state->bytes_received += n; - u->state->response_length += n; - - if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) { - ngx_http_upstream_finalize_request(r, u, NGX_ERROR); - return; - } - - if (!rev->ready) { - break; - } - } - - if (u->length == 0) { - ngx_http_upstream_finalize_request(r, u, 0); - return; - } - - if (ngx_handle_read_event(rev, 0) != NGX_OK) { - ngx_http_upstream_finalize_request(r, u, NGX_ERROR); - return; - } - - if (rev->active) { - ngx_add_timer(rev, u->conf->read_timeout); - - } else if (rev->timer_set) { - ngx_del_timer(rev); - } -} - - -static void ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u) { ssize_t n; @@ -4449,12 +4351,6 @@ #endif - if (r->subrequest_in_memory - && u->headers_in.status_n >= NGX_HTTP_SPECIAL_RESPONSE) - { - u->buffer.last = u->buffer.pos; - } - r->read_event_handler = ngx_http_block_reading; if (rc == NGX_DECLINED) { @@ -4486,13 +4382,12 @@ } if (rc == 0) { - if (ngx_http_upstream_process_trailers(r, u) != NGX_OK) { - rc = NGX_ERROR; - flush = 1; - } - } - if (rc == 0) { + if (ngx_http_upstream_process_trailers(r, u) != NGX_OK) { + ngx_http_finalize_request(r, NGX_ERROR); + return; + } + rc = ngx_http_send_special(r, NGX_HTTP_LAST); } else if (flush) { @@ -5265,27 +5160,6 @@ } -static ngx_int_t -ngx_http_upstream_copy_trailer(ngx_http_request_t *r, - ngx_table_elt_t *h, ngx_uint_t offset) -{ - ngx_table_elt_t *ho; - - if (!r->expect_trailers || !r->upstream->conf->pass_trailers) { - return NGX_OK; - } - - ho = ngx_list_push(&r->headers_out.headers); - if (ho == NULL) { - return NGX_ERROR; - } - - *ho = *h; - - return NGX_OK; -} - - #if (NGX_HTTP_GZIP || NGX_COMPAT) static ngx_int_t
diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h index 89b5fde..921a4f2 100644 --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h
@@ -61,6 +61,7 @@ ngx_msec_t response_time; ngx_msec_t connect_time; ngx_msec_t header_time; + ngx_msec_t queue_time; off_t response_length; off_t bytes_received; off_t bytes_sent; @@ -187,8 +188,6 @@ ngx_array_t *hide_headers; ngx_array_t *pass_headers; - ngx_flag_t pass_trailers; - ngx_http_upstream_local_t *local; #if (NGX_HTTP_CACHE) @@ -224,6 +223,8 @@ signed store:2; unsigned intercept_404:1; unsigned change_buffering:1; + unsigned pass_trailers:1; + unsigned preserve_output:1; #if (NGX_HTTP_SSL || NGX_COMPAT) ngx_ssl_t *ssl; @@ -393,6 +394,7 @@ unsigned request_sent:1; unsigned request_body_sent:1; + unsigned request_body_blocked:1; unsigned header_sent:1; };
diff --git a/src/http/ngx_http_variables.c b/src/http/ngx_http_variables.c index 9f26a23..e886abc 100644 --- a/src/http/ngx_http_variables.c +++ b/src/http/ngx_http_variables.c
@@ -429,7 +429,9 @@ return NULL; } - v->flags &= flags | ~NGX_HTTP_VAR_WEAK; + if (!(flags & NGX_HTTP_VAR_WEAK)) { + v->flags &= ~NGX_HTTP_VAR_WEAK; + } return v; } @@ -494,7 +496,9 @@ return NULL; } - v->flags &= flags | ~NGX_HTTP_VAR_WEAK; + if (!(flags & NGX_HTTP_VAR_WEAK)) { + v->flags &= ~NGX_HTTP_VAR_WEAK; + } return v; }
diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index cea0bd9..360905c 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c
@@ -55,8 +55,6 @@ #define NGX_HTTP_V2_FRAME_BUFFER_SIZE 24 -#define NGX_HTTP_V2_DEFAULT_FRAME_SIZE (1 << 14) - #define NGX_HTTP_V2_ROOT (void *) -1 @@ -784,8 +782,8 @@ type, h2c->state.flags, h2c->state.length, h2c->state.sid); if (type >= NGX_HTTP_V2_FRAME_STATES) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "http2 frame with unknown type %ui", type); + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent frame with unknown type %ui", type); return ngx_http_v2_state_skip(h2c, pos, end); } @@ -2001,9 +1999,8 @@ return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "http2 SETTINGS frame params:%uz", - h2c->state.length / NGX_HTTP_V2_SETTINGS_PARAM_SIZE); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 SETTINGS frame"); return ngx_http_v2_state_settings_params(h2c, pos, end); } @@ -2170,9 +2167,8 @@ return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_ping); } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "http2 PING frame ack:%ud", - h2c->state.flags & NGX_HTTP_V2_ACK_FLAG ? 1 : 0); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 PING frame"); if (h2c->state.flags & NGX_HTTP_V2_ACK_FLAG) { return ngx_http_v2_state_skip(h2c, pos, end); @@ -3312,19 +3308,9 @@ continue; } - switch (ch) { - case '\0': - case LF: - case CR: - case ':': - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent invalid header name: \"%V\"", - &header->name); - - return NGX_ERROR; - } - - if (ch >= 'A' && ch <= 'Z') { + if (ch == '\0' || ch == LF || ch == CR || ch == ':' + || (ch >= 'A' && ch <= 'Z')) + { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent invalid header name: \"%V\"", &header->name); @@ -3338,10 +3324,7 @@ for (i = 0; i != header->value.len; i++) { ch = header->value.data[i]; - switch (ch) { - case '\0': - case LF: - case CR: + if (ch == '\0' || ch == LF || ch == CR) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent header \"%V\" with " "invalid value: \"%V\"",
diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h index d89e8fe..ebd0e77 100644 --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h
@@ -18,6 +18,7 @@ #define NGX_HTTP_V2_STATE_BUFFER_SIZE 16 +#define NGX_HTTP_V2_DEFAULT_FRAME_SIZE (1 << 14) #define NGX_HTTP_V2_MAX_FRAME_SIZE ((1 << 24) - 1) #define NGX_HTTP_V2_INT_OCTETS 4 @@ -291,6 +292,9 @@ ngx_int_t ngx_http_v2_send_output_queue(ngx_http_v2_connection_t *h2c); +ngx_str_t *ngx_http_v2_get_static_name(ngx_uint_t index); +ngx_str_t *ngx_http_v2_get_static_value(ngx_uint_t index); + ngx_int_t ngx_http_v2_get_indexed_header(ngx_http_v2_connection_t *h2c, ngx_uint_t index, ngx_uint_t name_only); ngx_int_t ngx_http_v2_add_header(ngx_http_v2_connection_t *h2c, @@ -357,4 +361,53 @@ #define ngx_http_v2_write_sid ngx_http_v2_write_uint32 + +#define ngx_http_v2_indexed(i) (128 + (i)) +#define ngx_http_v2_inc_indexed(i) (64 + (i)) + +#define ngx_http_v2_write_name(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 1) +#define ngx_http_v2_write_value(dst, src, len, tmp) \ + ngx_http_v2_string_encode(dst, src, len, tmp, 0) + +#define NGX_HTTP_V2_ENCODE_RAW 0 +#define NGX_HTTP_V2_ENCODE_HUFF 0x80 + +#define NGX_HTTP_V2_AUTHORITY_INDEX 1 + +#define NGX_HTTP_V2_METHOD_INDEX 2 +#define NGX_HTTP_V2_METHOD_GET_INDEX 2 +#define NGX_HTTP_V2_METHOD_POST_INDEX 3 + +#define NGX_HTTP_V2_PATH_INDEX 4 +#define NGX_HTTP_V2_PATH_ROOT_INDEX 4 + +#define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6 +#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7 + +#define NGX_HTTP_V2_STATUS_INDEX 8 +#define NGX_HTTP_V2_STATUS_200_INDEX 8 +#define NGX_HTTP_V2_STATUS_204_INDEX 9 +#define NGX_HTTP_V2_STATUS_206_INDEX 10 +#define NGX_HTTP_V2_STATUS_304_INDEX 11 +#define NGX_HTTP_V2_STATUS_400_INDEX 12 +#define NGX_HTTP_V2_STATUS_404_INDEX 13 +#define NGX_HTTP_V2_STATUS_500_INDEX 14 + +#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 +#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 +#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 +#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 +#define NGX_HTTP_V2_DATE_INDEX 33 +#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 +#define NGX_HTTP_V2_LOCATION_INDEX 46 +#define NGX_HTTP_V2_SERVER_INDEX 54 +#define NGX_HTTP_V2_USER_AGENT_INDEX 58 +#define NGX_HTTP_V2_VARY_INDEX 59 + + +u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, + u_char *tmp, ngx_uint_t lower); + + #endif /* _NGX_HTTP_V2_H_INCLUDED_ */
diff --git a/src/http/v2/ngx_http_v2_encode.c b/src/http/v2/ngx_http_v2_encode.c new file mode 100644 index 0000000..ac79208 --- /dev/null +++ b/src/http/v2/ngx_http_v2_encode.c
@@ -0,0 +1,62 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, + ngx_uint_t value); + + +u_char * +ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp, + ngx_uint_t lower) +{ + size_t hlen; + + hlen = ngx_http_v2_huff_encode(src, len, tmp, lower); + + if (hlen > 0) { + *dst = NGX_HTTP_V2_ENCODE_HUFF; + dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), hlen); + return ngx_cpymem(dst, tmp, hlen); + } + + *dst = NGX_HTTP_V2_ENCODE_RAW; + dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), len); + + if (lower) { + ngx_strlow(dst, src, len); + return dst + len; + } + + return ngx_cpymem(dst, src, len); +} + + +static u_char * +ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) +{ + if (value < prefix) { + *pos++ |= value; + return pos; + } + + *pos++ |= prefix; + value -= prefix; + + while (value >= 128) { + *pos++ = value % 128 + 128; + value /= 128; + } + + *pos++ = (u_char) value; + + return pos; +}
diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c index a2707a4..1b19b8a 100644 --- a/src/http/v2/ngx_http_v2_filter_module.c +++ b/src/http/v2/ngx_http_v2_filter_module.c
@@ -23,43 +23,6 @@ #define ngx_http_v2_literal_size(h) \ (ngx_http_v2_integer_octets(sizeof(h) - 1) + sizeof(h) - 1) -#define ngx_http_v2_indexed(i) (128 + (i)) -#define ngx_http_v2_inc_indexed(i) (64 + (i)) - -#define ngx_http_v2_write_name(dst, src, len, tmp) \ - ngx_http_v2_string_encode(dst, src, len, tmp, 1) -#define ngx_http_v2_write_value(dst, src, len, tmp) \ - ngx_http_v2_string_encode(dst, src, len, tmp, 0) - -#define NGX_HTTP_V2_ENCODE_RAW 0 -#define NGX_HTTP_V2_ENCODE_HUFF 0x80 - -#define NGX_HTTP_V2_AUTHORITY_INDEX 1 -#define NGX_HTTP_V2_METHOD_GET_INDEX 2 -#define NGX_HTTP_V2_PATH_INDEX 4 - -#define NGX_HTTP_V2_SCHEME_HTTP_INDEX 6 -#define NGX_HTTP_V2_SCHEME_HTTPS_INDEX 7 - -#define NGX_HTTP_V2_STATUS_INDEX 8 -#define NGX_HTTP_V2_STATUS_200_INDEX 8 -#define NGX_HTTP_V2_STATUS_204_INDEX 9 -#define NGX_HTTP_V2_STATUS_206_INDEX 10 -#define NGX_HTTP_V2_STATUS_304_INDEX 11 -#define NGX_HTTP_V2_STATUS_400_INDEX 12 -#define NGX_HTTP_V2_STATUS_404_INDEX 13 -#define NGX_HTTP_V2_STATUS_500_INDEX 14 - -#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 -#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 -#define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 -#define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 -#define NGX_HTTP_V2_DATE_INDEX 33 -#define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 -#define NGX_HTTP_V2_LOCATION_INDEX 46 -#define NGX_HTTP_V2_SERVER_INDEX 54 -#define NGX_HTTP_V2_USER_AGENT_INDEX 58 -#define NGX_HTTP_V2_VARY_INDEX 59 #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 @@ -93,10 +56,6 @@ static ngx_int_t ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, ngx_str_t *binary); -static u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, - u_char *tmp, ngx_uint_t lower); -static u_char *ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, - ngx_uint_t value); static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame( ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin); static ngx_http_v2_out_frame_t *ngx_http_v2_create_push_frame( @@ -177,7 +136,7 @@ u_char status, *pos, *start, *p, *tmp; size_t len, tmp_len; ngx_str_t host, location; - ngx_uint_t i, port; + ngx_uint_t i, port, fin; ngx_list_part_t *part; ngx_table_elt_t *header; ngx_connection_t *fc; @@ -693,7 +652,10 @@ header[i].value.len, tmp); } - frame = ngx_http_v2_create_headers_frame(r, start, pos, r->header_only); + fin = r->header_only + || (r->headers_out.content_length_n == 0 && !r->expect_trailers); + + frame = ngx_http_v2_create_headers_frame(r, start, pos, fin); if (frame == NULL) { return NGX_ERROR; } @@ -1120,54 +1082,6 @@ } -static u_char * -ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp, - ngx_uint_t lower) -{ - size_t hlen; - - hlen = ngx_http_v2_huff_encode(src, len, tmp, lower); - - if (hlen > 0) { - *dst = NGX_HTTP_V2_ENCODE_HUFF; - dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), hlen); - return ngx_cpymem(dst, tmp, hlen); - } - - *dst = NGX_HTTP_V2_ENCODE_RAW; - dst = ngx_http_v2_write_int(dst, ngx_http_v2_prefix(7), len); - - if (lower) { - ngx_strlow(dst, src, len); - return dst + len; - } - - return ngx_cpymem(dst, src, len); -} - - -static u_char * -ngx_http_v2_write_int(u_char *pos, ngx_uint_t prefix, ngx_uint_t value) -{ - if (value < prefix) { - *pos++ |= value; - return pos; - } - - *pos++ |= prefix; - value -= prefix; - - while (value >= 128) { - *pos++ = value % 128 + 128; - value /= 128; - } - - *pos++ = (u_char) value; - - return pos; -} - - static ngx_http_v2_out_frame_t * ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin) @@ -1264,9 +1178,9 @@ cl->next = NULL; frame->last = cl; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http2:%ui create HEADERS frame %p: len:%uz", - stream->node->id, frame, frame->length); + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2:%ui create HEADERS frame %p: len:%uz fin:%ui", + stream->node->id, frame, frame->length, fin); return frame; } @@ -1535,7 +1449,7 @@ in = in->next; } - if (in == NULL) { + if (in == NULL || stream->out_closed) { if (stream->queued) { fc->write->active = 1;
diff --git a/src/http/v2/ngx_http_v2_table.c b/src/http/v2/ngx_http_v2_table.c index 62025c4..7d49803 100644 --- a/src/http/v2/ngx_http_v2_table.c +++ b/src/http/v2/ngx_http_v2_table.c
@@ -86,6 +86,20 @@ / sizeof(ngx_http_v2_header_t)) +ngx_str_t * +ngx_http_v2_get_static_name(ngx_uint_t index) +{ + return &ngx_http_v2_static_table[index - 1].name; +} + + +ngx_str_t * +ngx_http_v2_get_static_value(ngx_uint_t index) +{ + return &ngx_http_v2_static_table[index - 1].value; +} + + ngx_int_t ngx_http_v2_get_indexed_header(ngx_http_v2_connection_t *h2c, ngx_uint_t index, ngx_uint_t name_only)
diff --git a/src/ngx_modules.c b/src/ngx_modules.c index bdda69a..cd1db63 100644 --- a/src/ngx_modules.c +++ b/src/ngx_modules.c
@@ -123,6 +123,9 @@ #if (NGX_HTTP_SCGI) extern ngx_module_t ngx_http_scgi_module; #endif +#if (NGX_HTTP_GRPC) +extern ngx_module_t ngx_http_grpc_module; +#endif #if 0 extern ngx_module_t ngx_http_perl_module; #endif @@ -403,6 +406,9 @@ #if (NGX_HTTP_SCGI) &ngx_http_scgi_module, #endif +#if (NGX_HTTP_GRPC) + &ngx_http_grpc_module, +#endif #if 0 &ngx_http_perl_module, #endif @@ -685,6 +691,9 @@ #if (NGX_HTTP_SCGI) "ngx_http_scgi_module", #endif +#if (NGX_HTTP_GRPC) + "ngx_http_grpc_module", +#endif #if 0 "ngx_http_perl_module", #endif @@ -1010,6 +1019,9 @@ #if !(NGX_HTTP_GEO) ngx_write_stderr(" --without-http_geo_module"); #endif +#if !(NGX_HTTP_GRPC) + ngx_write_stderr(" --without-http_grpc_module"); +#endif #if !(NGX_HTTP_GZIP_FILTER) ngx_write_stderr(" --without-http_gzip_module"); #endif
diff --git a/src/os/unix/ngx_freebsd_sendfile_chain.c b/src/os/unix/ngx_freebsd_sendfile_chain.c index 4822e72..3d415bd 100644 --- a/src/os/unix/ngx_freebsd_sendfile_chain.c +++ b/src/os/unix/ngx_freebsd_sendfile_chain.c
@@ -135,7 +135,7 @@ if (ngx_freebsd_use_tcp_nopush && c->tcp_nopush == NGX_TCP_NOPUSH_UNSET) { - if (ngx_tcp_nopush(c->fd) == NGX_ERROR) { + if (ngx_tcp_nopush(c->fd) == -1) { err = ngx_socket_errno; /*
diff --git a/src/os/unix/ngx_linux_sendfile_chain.c b/src/os/unix/ngx_linux_sendfile_chain.c index b44724c..5695839 100644 --- a/src/os/unix/ngx_linux_sendfile_chain.c +++ b/src/os/unix/ngx_linux_sendfile_chain.c
@@ -130,7 +130,7 @@ if (c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) { - if (ngx_tcp_nopush(c->fd) == NGX_ERROR) { + if (ngx_tcp_nopush(c->fd) == -1) { err = ngx_socket_errno; /*
diff --git a/src/stream/ngx_stream_geo_module.c b/src/stream/ngx_stream_geo_module.c index 632fa5a..83f7fb4 100644 --- a/src/stream/ngx_stream_geo_module.c +++ b/src/stream/ngx_stream_geo_module.c
@@ -341,18 +341,18 @@ static char * ngx_stream_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - char *rv; - size_t len; - ngx_str_t *value, name; - ngx_uint_t i; - ngx_conf_t save; - ngx_pool_t *pool; - ngx_array_t *a; - ngx_stream_variable_t *var; - ngx_stream_geo_ctx_t *geo; - ngx_stream_geo_conf_ctx_t ctx; + char *rv; + size_t len; + ngx_str_t *value, name; + ngx_uint_t i; + ngx_conf_t save; + ngx_pool_t *pool; + ngx_array_t *a; + ngx_stream_variable_t *var; + ngx_stream_geo_ctx_t *geo; + ngx_stream_geo_conf_ctx_t ctx; #if (NGX_HAVE_INET6) - static struct in6_addr zero; + static struct in6_addr zero; #endif value = cf->args->elts; @@ -409,6 +409,7 @@ ctx.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); if (ctx.temp_pool == NULL) { + ngx_destroy_pool(pool); return NGX_CONF_ERROR; } @@ -430,6 +431,10 @@ *cf = save; + if (rv != NGX_CONF_OK) { + goto failed; + } + if (ctx.ranges) { if (ctx.high.low && !ctx.binary_include) { @@ -449,7 +454,7 @@ ctx.high.low[i] = ngx_palloc(cf->pool, len + sizeof(void *)); if (ctx.high.low[i] == NULL) { - return NGX_CONF_ERROR; + goto failed; } ngx_memcpy(ctx.high.low[i], a->elts, len); @@ -475,14 +480,11 @@ var->get_handler = ngx_stream_geo_range_variable; var->data = (uintptr_t) geo; - ngx_destroy_pool(ctx.temp_pool); - ngx_destroy_pool(pool); - } else { if (ctx.tree == NULL) { ctx.tree = ngx_radix_tree_create(cf->pool, -1); if (ctx.tree == NULL) { - return NGX_CONF_ERROR; + goto failed; } } @@ -492,7 +494,7 @@ if (ctx.tree6 == NULL) { ctx.tree6 = ngx_radix_tree_create(cf->pool, -1); if (ctx.tree6 == NULL) { - return NGX_CONF_ERROR; + goto failed; } } @@ -502,14 +504,11 @@ var->get_handler = ngx_stream_geo_cidr_variable; var->data = (uintptr_t) geo; - ngx_destroy_pool(ctx.temp_pool); - ngx_destroy_pool(pool); - if (ngx_radix32tree_insert(ctx.tree, 0, 0, (uintptr_t) &ngx_stream_variable_null_value) == NGX_ERROR) { - return NGX_CONF_ERROR; + goto failed; } /* NGX_BUSY is okay (default was set explicitly) */ @@ -519,12 +518,22 @@ (uintptr_t) &ngx_stream_variable_null_value) == NGX_ERROR) { - return NGX_CONF_ERROR; + goto failed; } #endif } - return rv; + ngx_destroy_pool(ctx.temp_pool); + ngx_destroy_pool(pool); + + return NGX_CONF_OK; + +failed: + + ngx_destroy_pool(ctx.temp_pool); + ngx_destroy_pool(pool); + + return NGX_CONF_ERROR; }
diff --git a/src/stream/ngx_stream_log_module.c b/src/stream/ngx_stream_log_module.c index 466bdda..0ff7f42 100644 --- a/src/stream/ngx_stream_log_module.c +++ b/src/stream/ngx_stream_log_module.c
@@ -89,6 +89,11 @@ } ngx_stream_log_var_t; +#define NGX_STREAM_LOG_ESCAPE_DEFAULT 0 +#define NGX_STREAM_LOG_ESCAPE_JSON 1 +#define NGX_STREAM_LOG_ESCAPE_NONE 2 + + static void ngx_stream_log_write(ngx_stream_session_t *s, ngx_stream_log_t *log, u_char *buf, size_t len); static ssize_t ngx_stream_log_script_write(ngx_stream_session_t *s, @@ -106,7 +111,7 @@ static void ngx_stream_log_flush_handler(ngx_event_t *ev); static ngx_int_t ngx_stream_log_variable_compile(ngx_conf_t *cf, - ngx_stream_log_op_t *op, ngx_str_t *value, ngx_uint_t json); + ngx_stream_log_op_t *op, ngx_str_t *value, ngx_uint_t escape); static size_t ngx_stream_log_variable_getlen(ngx_stream_session_t *s, uintptr_t data); static u_char *ngx_stream_log_variable(ngx_stream_session_t *s, u_char *buf, @@ -116,6 +121,10 @@ uintptr_t data); static u_char *ngx_stream_log_json_variable(ngx_stream_session_t *s, u_char *buf, ngx_stream_log_op_t *op); +static size_t ngx_stream_log_unescaped_variable_getlen(ngx_stream_session_t *s, + uintptr_t data); +static u_char *ngx_stream_log_unescaped_variable(ngx_stream_session_t *s, + u_char *buf, ngx_stream_log_op_t *op); static void *ngx_stream_log_create_main_conf(ngx_conf_t *cf); @@ -682,7 +691,7 @@ static ngx_int_t ngx_stream_log_variable_compile(ngx_conf_t *cf, ngx_stream_log_op_t *op, - ngx_str_t *value, ngx_uint_t json) + ngx_str_t *value, ngx_uint_t escape) { ngx_int_t index; @@ -693,11 +702,18 @@ op->len = 0; - if (json) { + switch (escape) { + case NGX_STREAM_LOG_ESCAPE_JSON: op->getlen = ngx_stream_log_json_variable_getlen; op->run = ngx_stream_log_json_variable; + break; - } else { + case NGX_STREAM_LOG_ESCAPE_NONE: + op->getlen = ngx_stream_log_unescaped_variable_getlen; + op->run = ngx_stream_log_unescaped_variable; + break; + + default: /* NGX_STREAM_LOG_ESCAPE_DEFAULT */ op->getlen = ngx_stream_log_variable_getlen; op->run = ngx_stream_log_variable; } @@ -851,6 +867,40 @@ } +static size_t +ngx_stream_log_unescaped_variable_getlen(ngx_stream_session_t *s, + uintptr_t data) +{ + ngx_stream_variable_value_t *value; + + value = ngx_stream_get_indexed_variable(s, data); + + if (value == NULL || value->not_found) { + return 0; + } + + value->escape = 0; + + return value->len; +} + + +static u_char * +ngx_stream_log_unescaped_variable(ngx_stream_session_t *s, u_char *buf, + ngx_stream_log_op_t *op) +{ + ngx_stream_variable_value_t *value; + + value = ngx_stream_get_indexed_variable(s, op->data); + + if (value == NULL || value->not_found) { + return buf; + } + + return ngx_cpymem(buf, value->data, value->len); +} + + static void * ngx_stream_log_create_main_conf(ngx_conf_t *cf) { @@ -1265,17 +1315,20 @@ size_t i, len; ngx_str_t *value, var; ngx_int_t *flush; - ngx_uint_t bracket, json; + ngx_uint_t bracket, escape; ngx_stream_log_op_t *op; - json = 0; + escape = NGX_STREAM_LOG_ESCAPE_DEFAULT; value = args->elts; if (s < args->nelts && ngx_strncmp(value[s].data, "escape=", 7) == 0) { data = value[s].data + 7; if (ngx_strcmp(data, "json") == 0) { - json = 1; + escape = NGX_STREAM_LOG_ESCAPE_JSON; + + } else if (ngx_strcmp(data, "none") == 0) { + escape = NGX_STREAM_LOG_ESCAPE_NONE; } else if (ngx_strcmp(data, "default") != 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, @@ -1350,7 +1403,7 @@ goto invalid; } - if (ngx_stream_log_variable_compile(cf, op, &var, json) + if (ngx_stream_log_variable_compile(cf, op, &var, escape) != NGX_OK) { return NGX_CONF_ERROR;
diff --git a/src/stream/ngx_stream_ssl_preread_module.c b/src/stream/ngx_stream_ssl_preread_module.c index e3d11fd..62d6524 100644 --- a/src/stream/ngx_stream_ssl_preread_module.c +++ b/src/stream/ngx_stream_ssl_preread_module.c
@@ -17,10 +17,12 @@ typedef struct { size_t left; size_t size; + size_t ext; u_char *pos; u_char *dst; u_char buf[4]; ngx_str_t host; + ngx_str_t alpn; ngx_log_t *log; ngx_pool_t *pool; ngx_uint_t state; @@ -32,6 +34,8 @@ ngx_stream_ssl_preread_ctx_t *ctx, u_char *pos, u_char *last); static ngx_int_t ngx_stream_ssl_preread_server_name_variable( ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_ssl_preread_alpn_protocols_variable( + ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_ssl_preread_add_variables(ngx_conf_t *cf); static void *ngx_stream_ssl_preread_create_srv_conf(ngx_conf_t *cf); static char *ngx_stream_ssl_preread_merge_srv_conf(ngx_conf_t *cf, void *parent, @@ -85,6 +89,9 @@ { ngx_string("ssl_preread_server_name"), NULL, ngx_stream_ssl_preread_server_name_variable, 0, 0, 0 }, + { ngx_string("ssl_preread_alpn_protocols"), NULL, + ngx_stream_ssl_preread_alpn_protocols_variable, 0, 0, 0 }, + ngx_stream_null_variable }; @@ -139,12 +146,14 @@ if (p[0] != 0x16) { ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, "ssl preread: not a handshake"); + ngx_stream_set_ctx(s, NULL, ngx_stream_ssl_preread_module); return NGX_DECLINED; } if (p[1] != 3) { ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, "ssl preread: unsupported SSL version"); + ngx_stream_set_ctx(s, NULL, ngx_stream_ssl_preread_module); return NGX_DECLINED; } @@ -158,6 +167,12 @@ p += 5; rc = ngx_stream_ssl_preread_parse_record(ctx, p, p + len); + + if (rc == NGX_DECLINED) { + ngx_stream_set_ctx(s, NULL, ngx_stream_ssl_preread_module); + return NGX_DECLINED; + } + if (rc != NGX_AGAIN) { return rc; } @@ -175,7 +190,7 @@ ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx, u_char *pos, u_char *last) { - size_t left, n, size; + size_t left, n, size, ext; u_char *dst, *p; enum { @@ -192,7 +207,10 @@ sw_ext_header, /* extension_type, extension_data length */ sw_sni_len, /* SNI length */ sw_sni_host_head, /* SNI name_type, host_name length */ - sw_sni_host /* SNI host_name */ + sw_sni_host, /* SNI host_name */ + sw_alpn_len, /* ALPN length */ + sw_alpn_proto_len, /* ALPN protocol_name length */ + sw_alpn_proto_data /* ALPN protocol_name */ } state; ngx_log_debug2(NGX_LOG_DEBUG_STREAM, ctx->log, 0, @@ -201,6 +219,7 @@ state = ctx->state; size = ctx->size; left = ctx->left; + ext = ctx->ext; dst = ctx->dst; p = ctx->buf; @@ -299,10 +318,18 @@ break; case sw_ext_header: - if (p[0] == 0 && p[1] == 0) { + if (p[0] == 0 && p[1] == 0 && ctx->host.data == NULL) { /* SNI extension */ state = sw_sni_len; - dst = NULL; + dst = p; + size = 2; + break; + } + + if (p[0] == 0 && p[1] == 16 && ctx->alpn.data == NULL) { + /* ALPN extension */ + state = sw_alpn_len; + dst = p; size = 2; break; } @@ -313,6 +340,7 @@ break; case sw_sni_len: + ext = (p[0] << 8) + p[1]; state = sw_sni_host_head; dst = p; size = 3; @@ -325,14 +353,21 @@ return NGX_DECLINED; } - state = sw_sni_host; size = (p[1] << 8) + p[2]; + if (ext < 3 + size) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: SNI format error"); + return NGX_DECLINED; + } + ext -= 3 + size; + ctx->host.data = ngx_pnalloc(ctx->pool, size); if (ctx->host.data == NULL) { return NGX_ERROR; } + state = sw_sni_host; dst = ctx->host.data; break; @@ -341,19 +376,77 @@ ngx_log_debug1(NGX_LOG_DEBUG_STREAM, ctx->log, 0, "ssl preread: SNI hostname \"%V\"", &ctx->host); - return NGX_OK; + + state = sw_ext; + dst = NULL; + size = ext; + break; + + case sw_alpn_len: + ext = (p[0] << 8) + p[1]; + + ctx->alpn.data = ngx_pnalloc(ctx->pool, ext); + if (ctx->alpn.data == NULL) { + return NGX_ERROR; + } + + state = sw_alpn_proto_len; + dst = p; + size = 1; + break; + + case sw_alpn_proto_len: + size = p[0]; + + if (size == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: ALPN empty protocol"); + return NGX_DECLINED; + } + + if (ext < 1 + size) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: ALPN format error"); + return NGX_DECLINED; + } + ext -= 1 + size; + + state = sw_alpn_proto_data; + dst = ctx->alpn.data + ctx->alpn.len; + break; + + case sw_alpn_proto_data: + ctx->alpn.len += p[0]; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: ALPN protocols \"%V\"", &ctx->alpn); + + if (ext) { + ctx->alpn.data[ctx->alpn.len++] = ','; + + state = sw_alpn_proto_len; + dst = p; + size = 1; + break; + } + + state = sw_ext; + dst = NULL; + size = 0; + break; } if (left < size) { - ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, - "ssl preread: failed to parse handshake"); - return NGX_DECLINED; + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, ctx->log, 0, + "ssl preread: failed to parse handshake"); + return NGX_DECLINED; } } ctx->state = state; ctx->size = size; ctx->left = left; + ctx->ext = ext; ctx->dst = dst; return NGX_AGAIN; @@ -384,6 +477,29 @@ static ngx_int_t +ngx_stream_ssl_preread_alpn_protocols_variable(ngx_stream_session_t *s, + ngx_variable_value_t *v, uintptr_t data) +{ + ngx_stream_ssl_preread_ctx_t *ctx; + + ctx = ngx_stream_get_module_ctx(s, ngx_stream_ssl_preread_module); + + if (ctx == NULL) { + v->not_found = 1; + return NGX_OK; + } + + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->len = ctx->alpn.len; + v->data = ctx->alpn.data; + + return NGX_OK; +} + + +static ngx_int_t ngx_stream_ssl_preread_add_variables(ngx_conf_t *cf) { ngx_stream_variable_t *var, *v;
diff --git a/src/stream/ngx_stream_variables.c b/src/stream/ngx_stream_variables.c index 95ae12b..d1526a9 100644 --- a/src/stream/ngx_stream_variables.c +++ b/src/stream/ngx_stream_variables.c
@@ -161,7 +161,9 @@ return NULL; } - v->flags &= flags | ~NGX_STREAM_VAR_WEAK; + if (!(flags & NGX_STREAM_VAR_WEAK)) { + v->flags &= ~NGX_STREAM_VAR_WEAK; + } return v; } @@ -227,7 +229,9 @@ return NULL; } - v->flags &= flags | ~NGX_STREAM_VAR_WEAK; + if (!(flags & NGX_STREAM_VAR_WEAK)) { + v->flags &= ~NGX_STREAM_VAR_WEAK; + } return v; }