diff --git a/.github/workflows/test-nginx-core.yml b/.github/workflows/test-nginx-core.yml index bfb1d2c6b1..13e67dacca 100644 --- a/.github/workflows/test-nginx-core.yml +++ b/.github/workflows/test-nginx-core.yml @@ -47,8 +47,9 @@ jobs: # NOTE: # For "-D T_NGX_MODIFY_DEFAULT_VALUE=0", we dont compile the source included in this macro, otherwise some nginx-tests cases tests will fail. + # For "-D T_NGX_SERVER_INFO=0", it made some cases (userid.t) passed. ./configure \ - --with-cc-opt="-D T_NGX_MODIFY_DEFAULT_VALUE=0 -D T_NGX_HTTP_IMAGE_FILTER=0" \ + --with-cc-opt="-D T_NGX_MODIFY_DEFAULT_VALUE=0 -D T_NGX_HTTP_IMAGE_FILTER=0 -D T_NGX_SERVER_INFO=0" \ --with-ld-opt="-Wl,-rpath,/usr/local/lib" \ --with-openssl-async \ --with-pcre \ @@ -84,7 +85,6 @@ jobs: --add-module=modules/mod_config \ --add-module=modules/mod_dubbo \ --add-module=modules/ngx_backtrace_module \ - --add-module=modules/ngx_debug_pool \ --add-module=modules/ngx_debug_timer \ --add-module=modules/ngx_http_concat_module \ --add-module=modules/ngx_http_footer_filter_module \ @@ -106,6 +106,7 @@ jobs: --with-stream_sni \ --with-openssl-async \ --with-debug + # skip ngx_debug_pool, it modified NGX_MIN_POOL_SIZE, which made some test case failed (http_header_buffers.t) # skip tengine upstream keepalive module #--without-http_upstream_keepalive_module \ #--add-module=modules/ngx_http_upstream_keepalive_module \ @@ -119,7 +120,11 @@ jobs: TEST_NGINX_BINARY: /usr/local/nginx/sbin/nginx TEST_NGINX_UNSAFE: yes run: | + # prepare perl library for test case sudo cpanm --notest SCGI Protocol::WebSocket Net::SSLeay IO::Socket::SSL Cache::Memcached Cache::Memcached::Fast Net::DNS::Nameserver GD > build.log 2>&1 || (cat build.log && exit 1) + # fixed http_method.t for tengine proxy_connect module + sed -i -e "s+405 Not Allowed(?!.*200 OK)/s, 'connect'+400 Bad Request(?!.*200 OK)/s, 'connect'+" nginx-tests/http_method.t + # run cases in nginx-tests prove -I nginx-tests/lib nginx-tests/ # It must be root for some cases. sudo groupadd wheel # for proxy_bind_transparent.t diff --git a/README.markdown b/README.markdown index b80979ec6b..2578171632 100644 --- a/README.markdown +++ b/README.markdown @@ -9,7 +9,7 @@ Tengine has been an open source project since December 2011. It is being activel Features ======== -* All features of nginx-1.18.0 are inherited, i.e., it is 100% compatible with nginx. +* All features of nginx-1.22.1 are inherited, i.e., it is 100% compatible with nginx. * Support the CONNECT HTTP method for forward proxy. * Support asynchronous OpenSSL, using hardware such as QAT for HTTPS acceleration. * Enhanced operations monitoring, such as asynchronous log & rollback, DNS caching, memory usage, etc. diff --git a/auto/configure b/auto/configure index 7e6e33a7c8..474d69e845 100755 --- a/auto/configure +++ b/auto/configure @@ -87,6 +87,10 @@ have=NGX_PID_PATH value="\"$NGX_PID_PATH\"" . auto/define have=NGX_LOCK_PATH value="\"$NGX_LOCK_PATH\"" . auto/define have=NGX_ERROR_LOG_PATH value="\"$NGX_ERROR_LOG_PATH\"" . auto/define +if [ ".$NGX_ERROR_LOG_PATH" = "." ]; then + have=NGX_ERROR_LOG_STDERR . auto/have +fi + have=NGX_HTTP_LOG_PATH value="\"$NGX_HTTP_LOG_PATH\"" . auto/define have=NGX_HTTP_CLIENT_TEMP_PATH value="\"$NGX_HTTP_CLIENT_TEMP_PATH\"" . auto/define diff --git a/auto/init b/auto/init index 910f5294b6..f816dfc4d8 100644 --- a/auto/init +++ b/auto/init @@ -48,4 +48,6 @@ default: build clean: rm -rf Makefile $NGX_OBJS + +.PHONY: default clean END diff --git a/auto/install b/auto/install index 50a69f1043..c764fdd2f7 100644 --- a/auto/install +++ b/auto/install @@ -204,7 +204,6 @@ build: install: \$(MAKE) -f $NGX_MAKEFILE install -.PHONY: modules modules: \$(MAKE) -f $NGX_MAKEFILE modules @@ -216,4 +215,6 @@ upgrade: test -f $NGX_PID_PATH.oldbin kill -QUIT \`cat $NGX_PID_PATH.oldbin\` + +.PHONY: build install modules upgrade END diff --git a/auto/make b/auto/make index a828d7e437..8b237a637f 100644 --- a/auto/make +++ b/auto/make @@ -317,7 +317,7 @@ $ngx_obj: \$(CORE_DEPS) \$(HTTP_DEPS)$ngx_cont$ngx_src END fi - done + done fi @@ -347,7 +347,7 @@ $ngx_obj: \$(CORE_DEPS) \$(MAIL_DEPS)$ngx_cont$ngx_src $ngx_cc$ngx_tab$ngx_objout$ngx_obj$ngx_tab$ngx_src$NGX_AUX END - done + done fi @@ -377,7 +377,7 @@ $ngx_obj: \$(CORE_DEPS) \$(STREAM_DEPS)$ngx_cont$ngx_src $ngx_cc$ngx_tab$ngx_objout$ngx_obj$ngx_tab$ngx_src$NGX_AUX END - done + done fi @@ -403,7 +403,7 @@ $ngx_obj: \$(CORE_DEPS) $ngx_cont$ngx_src $ngx_cc$ngx_tab$ngx_objout$ngx_obj$ngx_tab$ngx_src$NGX_AUX END - done + done fi @@ -435,7 +435,7 @@ $ngx_obj: \$(ADDON_DEPS)$ngx_cont$ngx_src $ngx_cc$ngx_tab$ngx_objout$ngx_obj$ngx_tab$ngx_src$NGX_AUX END - done + done fi @@ -506,6 +506,7 @@ fi for ngx_module in $DYNAMIC_MODULES do eval ngx_module_srcs="\$${ngx_module}_SRCS" + eval ngx_module_shrd="\$${ngx_module}_SHRD" eval eval ngx_module_libs="\\\"\$${ngx_module}_LIBS\\\"" eval ngx_module_modules="\$${ngx_module}_MODULES" @@ -571,7 +572,7 @@ END | sed -e "s/\(.*\.\)c/\1$ngx_objext/"` ngx_module_objs= - for ngx_src in $ngx_module_srcs + for ngx_src in $ngx_module_srcs $ngx_module_shrd do case "$ngx_src" in src/*) diff --git a/auto/module b/auto/module index a2b578db2c..3857d04ca9 100644 --- a/auto/module +++ b/auto/module @@ -17,7 +17,6 @@ if [ "$ngx_module_link" = DYNAMIC ]; then done DYNAMIC_MODULES="$DYNAMIC_MODULES $ngx_module" - eval ${ngx_module}_SRCS=\"$ngx_module_srcs\" eval ${ngx_module}_MODULES=\"$ngx_module_name\" @@ -31,6 +30,30 @@ if [ "$ngx_module_link" = DYNAMIC ]; then eval ${ngx_module}_ORDER=\"$ngx_module_order\" fi + srcs= + shrd= + for src in $ngx_module_srcs + do + found=no + for old in $DYNAMIC_MODULES_SRCS + do + if [ $src = $old ]; then + found=yes + break + fi + done + + if [ $found = no ]; then + srcs="$srcs $src" + else + shrd="$shrd $src" + fi + done + eval ${ngx_module}_SRCS=\"$srcs\" + eval ${ngx_module}_SHRD=\"$shrd\" + + DYNAMIC_MODULES_SRCS="$DYNAMIC_MODULES_SRCS $srcs" + if test -n "$ngx_module_incs"; then CORE_INCS="$CORE_INCS $ngx_module_incs" fi @@ -107,7 +130,24 @@ elif [ "$ngx_module_link" = ADDON ]; then eval ${ngx_module_type}_MODULES=\"\$${ngx_module_type}_MODULES \ $ngx_module_name\" - NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_module_srcs" + srcs= + for src in $ngx_module_srcs + do + found=no + for old in $NGX_ADDON_SRCS + do + if [ $src = $old ]; then + found=yes + break + fi + done + + if [ $found = no ]; then + srcs="$srcs $src" + fi + done + + NGX_ADDON_SRCS="$NGX_ADDON_SRCS $srcs" if test -n "$ngx_module_incs"; then eval ${ngx_var}_INCS=\"\$${ngx_var}_INCS $ngx_module_incs\" diff --git a/auto/modules b/auto/modules index 679c1b82c1..dcae810adb 100644 --- a/auto/modules +++ b/auto/modules @@ -113,6 +113,11 @@ if [ $HTTP = YES ]; then fi + if [ $HTTP_V2 = YES ]; then + HTTP_SRCS="$HTTP_SRCS $HTTP_HUFF_SRCS" + fi + + # the module order is important # ngx_http_static_module # ngx_http_gzip_static_module @@ -425,8 +430,6 @@ if [ $HTTP = YES ]; then 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" ngx_module_libs= ngx_module_link=$HTTP_V2 @@ -1004,6 +1007,12 @@ if [ $MAIL != NO ]; then ngx_module_srcs=src/mail/ngx_mail_proxy_module.c . auto/module + + ngx_module_name=ngx_mail_realip_module + ngx_module_deps= + ngx_module_srcs=src/mail/ngx_mail_realip_module.c + + . auto/module fi @@ -1138,6 +1147,16 @@ if [ $STREAM != NO ]; then . auto/module fi + if [ $STREAM_SET = YES ]; then + ngx_module_name=ngx_stream_set_module + ngx_module_deps= + ngx_module_srcs=src/stream/ngx_stream_set_module.c + ngx_module_libs= + ngx_module_link=$STREAM_SET + + . auto/module + fi + if [ $STREAM_UPSTREAM_HASH = YES ]; then ngx_module_name=ngx_stream_upstream_hash_module ngx_module_deps= diff --git a/auto/options b/auto/options index debb398972..ccd256cab4 100644 --- a/auto/options +++ b/auto/options @@ -138,8 +138,10 @@ STREAM_SSL_PREREAD=NO STREAM_SNI=NO DYNAMIC_MODULES= +DYNAMIC_MODULES_SRCS= NGX_ADDONS= +NGX_ADDON_SRCS= NGX_ADDON_DEPS= DYNAMIC_ADDONS= @@ -150,6 +152,7 @@ PCRE=NONE PCRE_OPT= PCRE_CONF_OPT= PCRE_JIT=NO +PCRE2=YES USE_OPENSSL=NO OPENSSL=NONE @@ -380,6 +383,7 @@ use the \"--with-mail_ssl_module\" option instead" --with-pcre=*) PCRE="$value" ;; --with-pcre-opt=*) PCRE_OPT="$value" ;; --with-pcre-jit) PCRE_JIT=YES ;; + --without-pcre2) PCRE2=DISABLED ;; --with-openssl=*) OPENSSL="$value" ;; --with-openssl-opt=*) OPENSSL_OPT="$value" ;; @@ -582,6 +586,7 @@ cat << END --without-stream_split_clients_module disable ngx_stream_split_clients_module --without-stream_return_module disable ngx_stream_return_module + --without-stream_set_module disable ngx_stream_set_module --without-stream_upstream_hash_module disable ngx_stream_upstream_hash_module --without-stream_upstream_least_conn_module @@ -612,6 +617,7 @@ cat << END --with-pcre=DIR set path to PCRE library sources --with-pcre-opt=OPTIONS set additional build options for PCRE --with-pcre-jit build PCRE with JIT compilation support + --without-pcre2 do not use PCRE2 library --with-zlib=DIR set path to zlib library sources --with-zlib-opt=OPTIONS set additional build options for zlib diff --git a/auto/os/freebsd b/auto/os/freebsd index 937ca204e6..870bac4b3e 100644 --- a/auto/os/freebsd +++ b/auto/os/freebsd @@ -44,12 +44,10 @@ if [ $osreldate -gt 300007 ]; then CORE_SRCS="$CORE_SRCS $FREEBSD_SENDFILE_SRCS" fi -if [ $NGX_FILE_AIO = YES ]; then - if [ $osreldate -gt 502103 ]; then - echo " + sendfile()'s SF_NODISKIO found" +if [ $osreldate -gt 1100093 ]; then + echo " + sendfile()'s SF_NODISKIO found" - have=NGX_HAVE_AIO_SENDFILE . auto/have - fi + have=NGX_HAVE_SENDFILE_NODISKIO . auto/have fi # POSIX semaphores diff --git a/auto/os/linux b/auto/os/linux index 5e280eca75..74b587026b 100644 --- a/auto/os/linux +++ b/auto/os/linux @@ -86,6 +86,31 @@ if [ $ngx_found = yes ]; then ee.data.ptr = NULL; epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ee)" . auto/feature + + + # eventfd() + + ngx_feature="eventfd()" + ngx_feature_name="NGX_HAVE_EVENTFD" + ngx_feature_run=no + ngx_feature_incs="#include " + ngx_feature_path= + ngx_feature_libs= + ngx_feature_test="(void) eventfd(0, 0)" + . auto/feature + + if [ $ngx_found = yes ]; then + have=NGX_HAVE_SYS_EVENTFD_H . auto/have + fi + + + if [ $ngx_found = no ]; then + + ngx_feature="eventfd() (SYS_eventfd)" + ngx_feature_incs="#include " + ngx_feature_test="(void) SYS_eventfd" + . auto/feature + fi fi diff --git a/auto/sources b/auto/sources index c488f39081..3134b8b0b4 100644 --- a/auto/sources +++ b/auto/sources @@ -272,7 +272,8 @@ NGX_WIN32_RC="src/os/win32/nginx.rc" HTTP_FILE_CACHE_SRCS=src/http/ngx_http_file_cache.c - +HTTP_HUFF_SRCS="src/http/ngx_http_huff_decode.c + src/http/ngx_http_huff_encode.c" NGX_HTTP_LUA_MODULE=ngx_http_lua_module NGX_HTTP_LUA_MODULE_SRCS="modules/ngx_http_lua_module/src/ngx_http_lua_script.c \ diff --git a/auto/summary b/auto/summary index 223bbda657..eabe5755c2 100644 --- a/auto/summary +++ b/auto/summary @@ -16,9 +16,9 @@ if [ $USE_PCRE = DISABLED ]; then else case $PCRE in - YES) echo " + using system PCRE library" ;; + YES) echo " + using system $PCRE_LIBRARY library" ;; NONE) echo " + PCRE library is not used" ;; - *) echo " + using PCRE library: $PCRE" ;; + *) echo " + using $PCRE_LIBRARY library: $PCRE" ;; esac fi diff --git a/auto/unix b/auto/unix index 5a8bd4b040..7c7d7bca69 100644 --- a/auto/unix +++ b/auto/unix @@ -582,29 +582,6 @@ Currently file AIO is supported on FreeBSD 4.3+ and Linux 2.6.22+ only END exit 1 fi - -else - - ngx_feature="eventfd()" - ngx_feature_name="NGX_HAVE_EVENTFD" - ngx_feature_run=no - ngx_feature_incs="#include " - ngx_feature_path= - ngx_feature_libs= - ngx_feature_test="(void) eventfd(0, 0)" - . auto/feature - - if [ $ngx_found = yes ]; then - have=NGX_HAVE_SYS_EVENTFD_H . auto/have - fi - - if [ $ngx_found = no ]; then - - ngx_feature="eventfd() (SYS_eventfd)" - ngx_feature_incs="#include " - ngx_feature_test="(void) SYS_eventfd" - . auto/feature - fi fi @@ -727,56 +704,44 @@ ngx_feature_test="char buf[1]; struct iovec vec[1]; ssize_t n; . auto/feature -ngx_feature="sys_nerr" -ngx_feature_name="NGX_SYS_NERR" -ngx_feature_run=value -ngx_feature_incs='#include - #include ' +# strerrordesc_np(), introduced in glibc 2.32 + +ngx_feature="strerrordesc_np()" +ngx_feature_name="NGX_HAVE_STRERRORDESC_NP" +ngx_feature_run=no +ngx_feature_incs='#include ' ngx_feature_path= ngx_feature_libs= -ngx_feature_test='printf("%d", sys_nerr);' +ngx_feature_test="char *p; p = strerrordesc_np(0); + if (p == NULL) return 1" . auto/feature if [ $ngx_found = no ]; then - # Cygiwn defines _sys_nerr - ngx_feature="_sys_nerr" + ngx_feature="sys_nerr" ngx_feature_name="NGX_SYS_NERR" ngx_feature_run=value ngx_feature_incs='#include #include ' ngx_feature_path= ngx_feature_libs= - ngx_feature_test='printf("%d", _sys_nerr);' + ngx_feature_test='printf("%d", sys_nerr);' . auto/feature fi if [ $ngx_found = no ]; then - # Solaris has no sys_nerr - ngx_feature='maximum errno' - ngx_feature_name=NGX_SYS_NERR + # Cygiwn defines _sys_nerr + ngx_feature="_sys_nerr" + ngx_feature_name="NGX_SYS_NERR" ngx_feature_run=value ngx_feature_incs='#include - #include #include ' ngx_feature_path= ngx_feature_libs= - ngx_feature_test='int n; - char *p; - for (n = 1; n < 1000; n++) { - errno = 0; - p = strerror(n); - if (errno == EINVAL - || p == NULL - || strncmp(p, "Unknown error", 13) == 0) - { - break; - } - } - printf("%d", n);' + ngx_feature_test='printf("%d", _sys_nerr);' . auto/feature fi @@ -1039,6 +1004,7 @@ ngx_feature_test='struct addrinfo *res; . auto/feature +# Tengine: ngx_feature="sysinfo()" ngx_feature_name="NGX_HAVE_SYSINFO" ngx_feature_run=no @@ -1083,7 +1049,7 @@ ngx_feature_test='int fd; . auto/feature -# Auto read nameserver from /etc/resolv.conf. +# Tengine: auto read nameserver from /etc/resolv.conf. if [ -f "/etc/resolv.conf" ]; then have=NGX_RESOLVER_FILE value="\"/etc/resolv.conf\"" . auto/define fi diff --git a/conf/mime.types b/conf/mime.types index 2961256950..1c00d701ae 100644 --- a/conf/mime.types +++ b/conf/mime.types @@ -15,6 +15,7 @@ types { text/vnd.wap.wml wml; text/x-component htc; + image/avif avif; image/png png; image/svg+xml svg svgz; image/tiff tif tiff; @@ -51,6 +52,7 @@ types { application/vnd.openxmlformats-officedocument.wordprocessingml.document docx; application/vnd.wap.wmlc wmlc; + application/wasm wasm; application/x-7z-compressed 7z; application/x-cocoa cco; application/x-java-archive-diff jardiff; diff --git a/modules/ngx_http_proxy_connect_module/ngx_http_proxy_connect_module.c b/modules/ngx_http_proxy_connect_module/ngx_http_proxy_connect_module.c index 619305ccd7..70cd8bc03c 100644 --- a/modules/ngx_http_proxy_connect_module/ngx_http_proxy_connect_module.c +++ b/modules/ngx_http_proxy_connect_module/ngx_http_proxy_connect_module.c @@ -2023,7 +2023,7 @@ ngx_http_proxy_connect_post_read_handler(ngx_http_request_t *r) if (!pclcf->accept_connect) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "proxy_connect: client sent connect method"); - return NGX_HTTP_BAD_REQUEST; + return NGX_HTTP_NOT_ALLOWED; } /* init ctx */ diff --git a/modules/ngx_http_proxy_connect_module/t/http_proxy_connect.t b/modules/ngx_http_proxy_connect_module/t/http_proxy_connect.t index 1bd66ba808..c99d76700a 100644 --- a/modules/ngx_http_proxy_connect_module/t/http_proxy_connect.t +++ b/modules/ngx_http_proxy_connect_module/t/http_proxy_connect.t @@ -171,7 +171,7 @@ like(http_connect_request('www.no-dns-reply.com', '80', '/'), qr/502/, '200 Conn like(http_connect_request('127.0.0.1', '9999', '/'), qr/403/, '200 Connection Established not allowed port'); like(http_get('/'), qr/backend server/, 'Get method: proxy_pass'); like(http_get('/hello'), qr/world/, 'Get method: return 200'); -like(http_connect_request('forbidden.example.com', '8080', '/'), qr/400 Bad Request/, 'forbid CONNECT request without proxy_connect command enabled'); +like(http_connect_request('forbidden.example.com', '8080', '/'), qr/405 Not Allowed/, 'forbid CONNECT request without proxy_connect command enabled'); # proxy_remote_address directive supports dynamic domain resolving. like(http_connect_request('proxy-remote-address-resolve-domain.com', '8081', '/'), diff --git a/modules/ngx_multi_upstream_module/ngx_http_multi_upstream_module.c b/modules/ngx_multi_upstream_module/ngx_http_multi_upstream_module.c index f8ed0168d3..e0269c2310 100644 --- a/modules/ngx_multi_upstream_module/ngx_http_multi_upstream_module.c +++ b/modules/ngx_multi_upstream_module/ngx_http_multi_upstream_module.c @@ -452,9 +452,6 @@ ngx_http_multi_upstream_init_connection(ngx_connection_t *c, fake_u->peer.connection = c; #if (NGX_HAVE_FILE_AIO || NGX_COMPAT) fake_u->output.aio_handler = u->output.aio_handler; -#if (NGX_HAVE_AIO_SENDFILE || NGX_COMPAT) - fake_u->output.aio_preload = u->output.aio_preload; -#endif #endif #if (NGX_THREADS || NGX_COMPAT) diff --git a/src/core/nginx.c b/src/core/nginx.c index 59b0ff1bd5..05d1f8c1d3 100755 --- a/src/core/nginx.c +++ b/src/core/nginx.c @@ -203,6 +203,7 @@ static ngx_uint_t ngx_show_configure; static ngx_uint_t ngx_no_ssl_init; #endif static u_char *ngx_prefix; +static u_char *ngx_error_log; static u_char *ngx_conf_file; static u_char *ngx_conf_params; static char *ngx_signal; @@ -250,7 +251,7 @@ main(int argc, char *const *argv) ngx_pid = ngx_getpid(); ngx_parent = ngx_getppid(); - log = ngx_log_init(ngx_prefix); + log = ngx_log_init(ngx_prefix, ngx_error_log); if (log == NULL) { return 1; } @@ -432,9 +433,9 @@ ngx_show_version_info(void) if (ngx_show_help) { ngx_write_stderr( - "Usage: nginx [-?hvVtTq] [-s signal] [-c filename] " - "[-p prefix] [-g directives]" NGX_LINEFEED - NGX_LINEFEED + "Usage: nginx [-?hvVtTq] [-s signal] [-p prefix]" NGX_LINEFEED + " [-e filename] [-c filename] [-g directives]" + NGX_LINEFEED NGX_LINEFEED "Options:" NGX_LINEFEED " -?,-h : this help" NGX_LINEFEED " -v : show version and exit" NGX_LINEFEED @@ -452,6 +453,12 @@ ngx_show_version_info(void) NGX_LINEFEED #else " -p prefix : set prefix path (default: NONE)" NGX_LINEFEED +#endif + " -e filename : set error log file (default: " +#ifdef NGX_ERROR_LOG_STDERR + "stderr)" NGX_LINEFEED +#else + NGX_ERROR_LOG_PATH ")" NGX_LINEFEED #endif " -c filename : set configuration file (default: " NGX_CONF_PATH ")" NGX_LINEFEED @@ -535,6 +542,7 @@ ngx_add_inherited_sockets(ngx_cycle_t *cycle) ngx_memzero(ls, sizeof(ngx_listening_t)); ls->fd = (ngx_socket_t) s; + ls->inherited = 1; } } @@ -862,6 +870,24 @@ ngx_get_options(int argc, char *const *argv) ngx_log_stderr(0, "option \"-p\" requires directory name"); return NGX_ERROR; + case 'e': + if (*p) { + ngx_error_log = p; + + } else if (argv[++i]) { + ngx_error_log = (u_char *) argv[i]; + + } else { + ngx_log_stderr(0, "option \"-e\" requires file name"); + return NGX_ERROR; + } + + if (ngx_strcmp(ngx_error_log, "stderr") == 0) { + ngx_error_log = (u_char *) ""; + } + + goto next; + case 'c': if (*p) { ngx_conf_file = p; @@ -1060,6 +1086,14 @@ ngx_process_options(ngx_cycle_t *cycle) } } + if (ngx_error_log) { + cycle->error_log.len = ngx_strlen(ngx_error_log); + cycle->error_log.data = ngx_error_log; + + } else { + ngx_str_set(&cycle->error_log, NGX_ERROR_LOG_PATH); + } + if (ngx_conf_params) { cycle->conf_param.len = ngx_strlen(ngx_conf_params); cycle->conf_param.data = ngx_conf_params; diff --git a/src/core/nginx.h b/src/core/nginx.h index 8aaf171ba4..ffa11ef055 100644 --- a/src/core/nginx.h +++ b/src/core/nginx.h @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1018000 -#define NGINX_VERSION "1.18.0" +#define nginx_version 1022001 +#define NGINX_VERSION "1.22.1" #define NGINX_VER "nginx/" NGINX_VERSION #define TENGINE "Tengine" diff --git a/src/core/ngx_buf.c b/src/core/ngx_buf.c index c3783c4467..811f24d963 100644 --- a/src/core/ngx_buf.c +++ b/src/core/ngx_buf.c @@ -203,16 +203,16 @@ ngx_chain_update_chains(ngx_pool_t *p, ngx_chain_t **free, ngx_chain_t **busy, while (*busy) { cl = *busy; - if (ngx_buf_size(cl->buf) != 0) { - break; - } - if (cl->buf->tag != tag) { *busy = cl->next; ngx_free_chain(p, cl); continue; } + if (ngx_buf_size(cl->buf) != 0) { + break; + } + cl->buf->pos = cl->buf->start; cl->buf->last = cl->buf->start; diff --git a/src/core/ngx_buf.h b/src/core/ngx_buf.h index 12781a7821..fdcd0cddf6 100644 --- a/src/core/ngx_buf.h +++ b/src/core/ngx_buf.h @@ -90,9 +90,6 @@ struct ngx_output_chain_ctx_s { #if (NGX_HAVE_FILE_AIO || NGX_COMPAT) ngx_output_chain_aio_pt aio_handler; -#if (NGX_HAVE_AIO_SENDFILE || NGX_COMPAT) - ssize_t (*aio_preload)(ngx_buf_t *file); -#endif #endif #if (NGX_THREADS || NGX_COMPAT) @@ -125,20 +122,20 @@ typedef struct { #define NGX_CHAIN_ERROR (ngx_chain_t *) NGX_ERROR -#define ngx_buf_in_memory(b) (b->temporary || b->memory || b->mmap) -#define ngx_buf_in_memory_only(b) (ngx_buf_in_memory(b) && !b->in_file) +#define ngx_buf_in_memory(b) ((b)->temporary || (b)->memory || (b)->mmap) +#define ngx_buf_in_memory_only(b) (ngx_buf_in_memory(b) && !(b)->in_file) #define ngx_buf_special(b) \ - ((b->flush || b->last_buf || b->sync) \ - && !ngx_buf_in_memory(b) && !b->in_file) + (((b)->flush || (b)->last_buf || (b)->sync) \ + && !ngx_buf_in_memory(b) && !(b)->in_file) #define ngx_buf_sync_only(b) \ - (b->sync \ - && !ngx_buf_in_memory(b) && !b->in_file && !b->flush && !b->last_buf) + ((b)->sync && !ngx_buf_in_memory(b) \ + && !(b)->in_file && !(b)->flush && !(b)->last_buf) #define ngx_buf_size(b) \ - (ngx_buf_in_memory(b) ? (off_t) (b->last - b->pos): \ - (b->file_last - b->file_pos)) + (ngx_buf_in_memory(b) ? (off_t) ((b)->last - (b)->pos): \ + ((b)->file_last - (b)->file_pos)) ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size); ngx_chain_t *ngx_create_chain_of_bufs(ngx_pool_t *pool, ngx_bufs_t *bufs); @@ -149,8 +146,8 @@ ngx_chain_t *ngx_create_chain_of_bufs(ngx_pool_t *pool, ngx_bufs_t *bufs); ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool); #define ngx_free_chain(pool, cl) \ - cl->next = pool->chain; \ - pool->chain = cl + (cl)->next = (pool)->chain; \ + (pool)->chain = (cl) diff --git a/src/core/ngx_conf_file.c b/src/core/ngx_conf_file.c index 6d1629e9b4..fec7bb83ab 100644 --- a/src/core/ngx_conf_file.c +++ b/src/core/ngx_conf_file.c @@ -1137,7 +1137,7 @@ ngx_conf_set_keyval_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) a = (ngx_array_t **) (p + cmd->offset); - if (*a == NULL) { + if (*a == NGX_CONF_UNSET_PTR || *a == NULL) { *a = ngx_array_create(cf->pool, 4, sizeof(ngx_keyval_t)); if (*a == NULL) { return NGX_CONF_ERROR; diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c index 36b0101364..5110497fdf 100644 --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -495,21 +495,24 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle) return NGX_ERROR; } - if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, - (const void *) &reuseaddr, sizeof(int)) - == -1) - { - ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, - "setsockopt(SO_REUSEADDR) %V failed", - &ls[i].addr_text); + if (ls[i].type != SOCK_DGRAM || !ngx_test_config) { - if (ngx_close_socket(s) == -1) { + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (const void *) &reuseaddr, sizeof(int)) + == -1) + { ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, - ngx_close_socket_n " %V failed", + "setsockopt(SO_REUSEADDR) %V failed", &ls[i].addr_text); - } - return NGX_ERROR; + if (ngx_close_socket(s) == -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + ngx_close_socket_n " %V failed", + &ls[i].addr_text); + } + + return NGX_ERROR; + } } #if (NGX_HAVE_REUSEPORT) @@ -1078,7 +1081,8 @@ ngx_close_listening_sockets(ngx_cycle_t *cycle) if (ls[i].sockaddr->sa_family == AF_UNIX && ngx_process <= NGX_PROCESS_MASTER - && ngx_new_binary == 0) + && ngx_new_binary == 0 + && (!ls[i].inherited || ngx_getppid() != ngx_parent)) { u_char *name = ls[i].addr_text.data + sizeof("unix:") - 1; @@ -1117,12 +1121,9 @@ ngx_get_connection(ngx_socket_t s, ngx_log_t *log) return NULL; } - c = ngx_cycle->free_connections; + ngx_drain_connections((ngx_cycle_t *) ngx_cycle); - if (c == NULL) { - ngx_drain_connections((ngx_cycle_t *) ngx_cycle); - c = ngx_cycle->free_connections; - } + c = ngx_cycle->free_connections; if (c == NULL) { ngx_log_error(NGX_LOG_ALERT, log, 0, @@ -1363,6 +1364,22 @@ ngx_drain_connections(ngx_cycle_t *cycle) ngx_queue_t *q; ngx_connection_t *c; + if (cycle->free_connection_n > cycle->connection_n / 16 + || cycle->reusable_connections_n == 0) + { + return; + } + + if (cycle->connections_reuse_time != ngx_time()) { + cycle->connections_reuse_time = ngx_time(); + + ngx_log_error(NGX_LOG_WARN, cycle->log, 0, + "%ui worker_connections are not enough, " + "reusing connections", + cycle->connection_n); + } + + c = NULL; n = ngx_max(ngx_min(32, cycle->reusable_connections_n / 8), 1); for (i = 0; i < n; i++) { @@ -1379,6 +1396,21 @@ ngx_drain_connections(ngx_cycle_t *cycle) c->close = 1; c->read->handler(c->read); } + + if (cycle->free_connection_n == 0 && c && c->reusable) { + + /* + * if no connections were freed, try to reuse the last + * connection again: this should free it as long as + * previous reuse moved it to lingering close + */ + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, c->log, 0, + "reusing connection again"); + + c->close = 1; + c->read->handler(c->read); + } } diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h index 37e628a3cc..850a12a098 100644 --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -45,8 +45,6 @@ struct ngx_listening_s { size_t pool_size; /* should be here because of the AcceptEx() preread */ size_t post_accept_buffer_size; - /* should be here because of the deferred accept */ - ngx_msec_t post_accept_timeout; ngx_listening_t *previous; ngx_connection_t *connection; @@ -179,6 +177,7 @@ struct ngx_connection_s { ngx_atomic_uint_t number; + ngx_msec_t start_time; ngx_uint_t requests; unsigned buffered:8; @@ -204,7 +203,7 @@ struct ngx_connection_s { unsigned num_async_fds:8; #endif -#if (NGX_HAVE_AIO_SENDFILE || NGX_COMPAT) +#if (NGX_HAVE_SENDFILE_NODISKIO || NGX_COMPAT) unsigned busy_count:2; #endif diff --git a/src/core/ngx_cycle.c b/src/core/ngx_cycle.c index f35911bf59..7faabb61c9 100755 --- a/src/core/ngx_cycle.c +++ b/src/core/ngx_cycle.c @@ -109,6 +109,15 @@ ngx_init_cycle(ngx_cycle_t *old_cycle) return NULL; } + cycle->error_log.len = old_cycle->error_log.len; + cycle->error_log.data = ngx_pnalloc(pool, old_cycle->error_log.len + 1); + if (cycle->error_log.data == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + ngx_cpystrn(cycle->error_log.data, old_cycle->error_log.data, + old_cycle->error_log.len + 1); + cycle->conf_file.len = old_cycle->conf_file.len; cycle->conf_file.data = ngx_pnalloc(pool, old_cycle->conf_file.len + 1); if (cycle->conf_file.data == NULL) { @@ -569,6 +578,7 @@ ngx_init_cycle(ngx_cycle_t *old_cycle) == NGX_OK) { nls[n].fd = ls[i].fd; + nls[n].inherited = ls[i].inherited; nls[n].previous = &ls[i]; ls[i].remain = 1; @@ -1071,6 +1081,7 @@ ngx_int_t ngx_create_pidfile(ngx_str_t *name, ngx_log_t *log) { size_t len; + ngx_int_t rc; ngx_uint_t create; ngx_file_t file; u_char pid[NGX_INT64_LEN + 2]; @@ -1095,11 +1106,13 @@ ngx_create_pidfile(ngx_str_t *name, ngx_log_t *log) return NGX_ERROR; } + rc = NGX_OK; + if (!ngx_test_config) { len = ngx_snprintf(pid, NGX_INT64_LEN + 2, "%P%N", ngx_pid) - pid; if (ngx_write_file(&file, pid, len, 0) == NGX_ERROR) { - return NGX_ERROR; + rc = NGX_ERROR; } } @@ -1108,7 +1121,7 @@ ngx_create_pidfile(ngx_str_t *name, ngx_log_t *log) ngx_close_file_n " \"%s\" failed", file.name.data); } - return NGX_OK; + return rc; } diff --git a/src/core/ngx_cycle.h b/src/core/ngx_cycle.h index b47fc7ee25..4f70c8e071 100755 --- a/src/core/ngx_cycle.h +++ b/src/core/ngx_cycle.h @@ -55,6 +55,7 @@ struct ngx_cycle_s { ngx_queue_t reusable_connections_queue; ngx_uint_t reusable_connections_n; + time_t connections_reuse_time; ngx_array_t listening; ngx_array_t paths; @@ -82,6 +83,7 @@ struct ngx_cycle_s { ngx_str_t conf_param; ngx_str_t conf_prefix; ngx_str_t prefix; + ngx_str_t error_log; ngx_str_t lock_file; ngx_str_t hostname; #if (NGX_SSL && NGX_SSL_ASYNC) diff --git a/src/core/ngx_hash.c b/src/core/ngx_hash.c index d9c157c1dc..8215c2717b 100644 --- a/src/core/ngx_hash.c +++ b/src/core/ngx_hash.c @@ -274,6 +274,10 @@ ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts) } for (n = 0; n < nelts; n++) { + if (names[n].key.data == NULL) { + continue; + } + if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *)) { ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0, diff --git a/src/core/ngx_log.c b/src/core/ngx_log.c index 4b9c179832..4defcdfa8e 100644 --- a/src/core/ngx_log.c +++ b/src/core/ngx_log.c @@ -315,7 +315,7 @@ ngx_log_errno(u_char *buf, u_char *last, ngx_err_t err) ngx_log_t * -ngx_log_init(u_char *prefix) +ngx_log_init(u_char *prefix, u_char *error_log) { u_char *p, *name; size_t nlen, plen; @@ -323,13 +323,11 @@ ngx_log_init(u_char *prefix) ngx_log.file = &ngx_log_file; ngx_log.log_level = NGX_LOG_NOTICE; - name = (u_char *) NGX_ERROR_LOG_PATH; - - /* - * we use ngx_strlen() here since BCC warns about - * condition is always false and unreachable code - */ + if (error_log == NULL) { + error_log = (u_char *) NGX_ERROR_LOG_PATH; + } + name = error_log; nlen = ngx_strlen(name); if (nlen == 0) { @@ -369,7 +367,7 @@ ngx_log_init(u_char *prefix) *p++ = '/'; } - ngx_cpystrn(p, (u_char *) NGX_ERROR_LOG_PATH, nlen + 1); + ngx_cpystrn(p, error_log, nlen + 1); p = name; } @@ -403,8 +401,7 @@ ngx_log_init(u_char *prefix) ngx_int_t ngx_log_open_default(ngx_cycle_t *cycle) { - ngx_log_t *log; - static ngx_str_t error_log = ngx_string(NGX_ERROR_LOG_PATH); + ngx_log_t *log; if (ngx_log_get_file_log(&cycle->new_log) != NULL) { return NGX_OK; @@ -425,7 +422,7 @@ ngx_log_open_default(ngx_cycle_t *cycle) log->log_level = NGX_LOG_ERR; - log->file = ngx_conf_open_file(cycle, &error_log); + log->file = ngx_conf_open_file(cycle, &cycle->error_log); if (log->file == NULL) { return NGX_ERROR; } diff --git a/src/core/ngx_log.h b/src/core/ngx_log.h index afb73bf71d..ab64a5ad05 100644 --- a/src/core/ngx_log.h +++ b/src/core/ngx_log.h @@ -228,7 +228,7 @@ void ngx_cdecl ngx_log_debug_core(ngx_log_t *log, ngx_err_t err, /*********************************/ -ngx_log_t *ngx_log_init(u_char *prefix); +ngx_log_t *ngx_log_init(u_char *prefix, u_char *error_log); void ngx_cdecl ngx_log_abort(ngx_err_t err, const char *fmt, ...); void ngx_cdecl ngx_log_stderr(ngx_err_t err, const char *fmt, ...); u_char *ngx_log_errno(u_char *buf, u_char *last, ngx_err_t err); diff --git a/src/core/ngx_module.h b/src/core/ngx_module.h index 8cf3210e8e..6fb455426b 100644 --- a/src/core/ngx_module.h +++ b/src/core/ngx_module.h @@ -41,7 +41,7 @@ #define NGX_MODULE_SIGNATURE_3 "0" #endif -#if (NGX_HAVE_AIO_SENDFILE || NGX_COMPAT) +#if (NGX_HAVE_SENDFILE_NODISKIO || NGX_COMPAT) #define NGX_MODULE_SIGNATURE_4 "1" #else #define NGX_MODULE_SIGNATURE_4 "0" diff --git a/src/core/ngx_output_chain.c b/src/core/ngx_output_chain.c index 5c3dbe8720..8570742539 100644 --- a/src/core/ngx_output_chain.c +++ b/src/core/ngx_output_chain.c @@ -29,10 +29,6 @@ static ngx_inline ngx_int_t ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf); -#if (NGX_HAVE_AIO_SENDFILE) -static ngx_int_t ngx_output_chain_aio_setup(ngx_output_chain_ctx_t *ctx, - ngx_file_t *file); -#endif static ngx_int_t ngx_output_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain, ngx_chain_t *in); static ngx_int_t ngx_output_chain_align_file_buf(ngx_output_chain_ctx_t *ctx, @@ -260,10 +256,6 @@ ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf) } #endif - if (buf->in_file && buf->file->directio) { - return 0; - } - sendfile = ctx->sendfile; #if (NGX_SENDFILE_LIMIT) @@ -272,6 +264,19 @@ ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf) sendfile = 0; } +#endif + +#if !(NGX_HAVE_SENDFILE_NODISKIO) + + /* + * With DIRECTIO, disable sendfile() unless sendfile(SF_NOCACHE) + * is available. + */ + + if (buf->in_file && buf->file->directio) { + sendfile = 0; + } + #endif if (!sendfile) { @@ -283,12 +288,6 @@ ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf) buf->in_file = 0; } -#if (NGX_HAVE_AIO_SENDFILE) - if (ctx->aio_preload && buf->in_file) { - (void) ngx_output_chain_aio_setup(ctx, buf->file); - } -#endif - if (ctx->need_in_memory && !ngx_buf_in_memory(buf)) { return 0; } @@ -301,28 +300,6 @@ ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf) } -#if (NGX_HAVE_AIO_SENDFILE) - -static ngx_int_t -ngx_output_chain_aio_setup(ngx_output_chain_ctx_t *ctx, ngx_file_t *file) -{ - ngx_event_aio_t *aio; - - if (file->aio == NULL && ngx_file_aio_init(file, ctx->pool) != NGX_OK) { - return NGX_ERROR; - } - - aio = file->aio; - - aio->data = ctx->filter_ctx; - aio->preload_handler = ctx->aio_preload; - - return NGX_OK; -} - -#endif - - static ngx_int_t ngx_output_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain, ngx_chain_t *in) @@ -803,6 +780,10 @@ ngx_chain_writer(void *data, ngx_chain_t *in) return NGX_ERROR; } + if (chain && c->write->ready) { + ngx_post_event(c->write, &ngx_posted_next_events); + } + for (cl = ctx->out; cl && cl != chain; /* void */) { ln = cl; cl = cl->next; diff --git a/src/core/ngx_rbtree.h b/src/core/ngx_rbtree.h index 97f0e3e118..e8c3582139 100644 --- a/src/core/ngx_rbtree.h +++ b/src/core/ngx_rbtree.h @@ -47,6 +47,9 @@ struct ngx_rbtree_s { (tree)->sentinel = s; \ (tree)->insert = i +#define ngx_rbtree_data(node, type, link) \ + (type *) ((u_char *) (node) - offsetof(type, link)) + void ngx_rbtree_insert(ngx_rbtree_t *tree, ngx_rbtree_node_t *node); void ngx_rbtree_delete(ngx_rbtree_t *tree, ngx_rbtree_node_t *node); diff --git a/src/core/ngx_regex.c b/src/core/ngx_regex.c index 52169f6553..bebf3b6a83 100644 --- a/src/core/ngx_regex.c +++ b/src/core/ngx_regex.c @@ -10,15 +10,22 @@ typedef struct { - ngx_flag_t pcre_jit; + ngx_flag_t pcre_jit; + ngx_list_t *studies; } ngx_regex_conf_t; +static ngx_inline void ngx_regex_malloc_init(ngx_pool_t *pool); +static ngx_inline void ngx_regex_malloc_done(void); + +#if (NGX_PCRE2) +static void * ngx_libc_cdecl ngx_regex_malloc(size_t size, void *data); +static void ngx_libc_cdecl ngx_regex_free(void *p, void *data); +#else static void * ngx_libc_cdecl ngx_regex_malloc(size_t size); static void ngx_libc_cdecl ngx_regex_free(void *p); -#if (NGX_HAVE_PCRE_JIT) -static void ngx_pcre_free_studies(void *data); #endif +static void ngx_regex_cleanup(void *data); static ngx_int_t ngx_regex_module_init(ngx_cycle_t *cycle); @@ -65,31 +72,196 @@ ngx_module_t ngx_regex_module = { }; -static ngx_pool_t *ngx_pcre_pool; -static ngx_list_t *ngx_pcre_studies; +static ngx_pool_t *ngx_regex_pool; +static ngx_list_t *ngx_regex_studies; +static ngx_uint_t ngx_regex_direct_alloc; + +#if (NGX_PCRE2) +static pcre2_compile_context *ngx_regex_compile_context; +static pcre2_match_data *ngx_regex_match_data; +static ngx_uint_t ngx_regex_match_data_size; +#endif void ngx_regex_init(void) { +#if !(NGX_PCRE2) pcre_malloc = ngx_regex_malloc; pcre_free = ngx_regex_free; +#endif } static ngx_inline void ngx_regex_malloc_init(ngx_pool_t *pool) { - ngx_pcre_pool = pool; + ngx_regex_pool = pool; + ngx_regex_direct_alloc = (pool == NULL) ? 1 : 0; } static ngx_inline void ngx_regex_malloc_done(void) { - ngx_pcre_pool = NULL; + ngx_regex_pool = NULL; + ngx_regex_direct_alloc = 0; +} + + +#if (NGX_PCRE2) + +ngx_int_t +ngx_regex_compile(ngx_regex_compile_t *rc) +{ + int n, errcode; + char *p; + u_char errstr[128]; + size_t erroff; + uint32_t options; + pcre2_code *re; + ngx_regex_elt_t *elt; + pcre2_general_context *gctx; + pcre2_compile_context *cctx; + + if (ngx_regex_compile_context == NULL) { + /* + * Allocate a compile context if not yet allocated. This uses + * direct allocations from heap, so the result can be cached + * even at runtime. + */ + + ngx_regex_malloc_init(NULL); + + gctx = pcre2_general_context_create(ngx_regex_malloc, ngx_regex_free, + NULL); + if (gctx == NULL) { + ngx_regex_malloc_done(); + goto nomem; + } + + cctx = pcre2_compile_context_create(gctx); + if (cctx == NULL) { + pcre2_general_context_free(gctx); + ngx_regex_malloc_done(); + goto nomem; + } + + ngx_regex_compile_context = cctx; + + pcre2_general_context_free(gctx); + ngx_regex_malloc_done(); + } + + options = 0; + + if (rc->options & NGX_REGEX_CASELESS) { + options |= PCRE2_CASELESS; + } + + if (rc->options & NGX_REGEX_MULTILINE) { + options |= PCRE2_MULTILINE; + } + + if (rc->options & ~(NGX_REGEX_CASELESS|NGX_REGEX_MULTILINE)) { + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "regex \"%V\" compilation failed: invalid options", + &rc->pattern) + - rc->err.data; + return NGX_ERROR; + } + + ngx_regex_malloc_init(rc->pool); + + re = pcre2_compile(rc->pattern.data, rc->pattern.len, options, + &errcode, &erroff, ngx_regex_compile_context); + + /* ensure that there is no current pool */ + ngx_regex_malloc_done(); + + if (re == NULL) { + pcre2_get_error_message(errcode, errstr, 128); + + if ((size_t) erroff == rc->pattern.len) { + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "pcre2_compile() failed: %s in \"%V\"", + errstr, &rc->pattern) + - rc->err.data; + + } else { + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "pcre2_compile() failed: %s in \"%V\" at \"%s\"", + errstr, &rc->pattern, rc->pattern.data + erroff) + - rc->err.data; + } + + return NGX_ERROR; + } + + rc->regex = re; + + /* do not study at runtime */ + + if (ngx_regex_studies != NULL) { + elt = ngx_list_push(ngx_regex_studies); + if (elt == NULL) { + goto nomem; + } + + elt->regex = rc->regex; + elt->name = rc->pattern.data; + } + + n = pcre2_pattern_info(re, PCRE2_INFO_CAPTURECOUNT, &rc->captures); + if (n < 0) { + p = "pcre2_pattern_info(\"%V\", PCRE2_INFO_CAPTURECOUNT) failed: %d"; + goto failed; + } + + if (rc->captures == 0) { + return NGX_OK; + } + + n = pcre2_pattern_info(re, PCRE2_INFO_NAMECOUNT, &rc->named_captures); + if (n < 0) { + p = "pcre2_pattern_info(\"%V\", PCRE2_INFO_NAMECOUNT) failed: %d"; + goto failed; + } + + if (rc->named_captures == 0) { + return NGX_OK; + } + + n = pcre2_pattern_info(re, PCRE2_INFO_NAMEENTRYSIZE, &rc->name_size); + if (n < 0) { + p = "pcre2_pattern_info(\"%V\", PCRE2_INFO_NAMEENTRYSIZE) failed: %d"; + goto failed; + } + + n = pcre2_pattern_info(re, PCRE2_INFO_NAMETABLE, &rc->names); + if (n < 0) { + p = "pcre2_pattern_info(\"%V\", PCRE2_INFO_NAMETABLE) failed: %d"; + goto failed; + } + + return NGX_OK; + +failed: + + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, p, &rc->pattern, n) + - rc->err.data; + return NGX_ERROR; + +nomem: + + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "regex \"%V\" compilation failed: no memory", + &rc->pattern) + - rc->err.data; + return NGX_ERROR; } +#else ngx_int_t ngx_regex_compile(ngx_regex_compile_t *rc) @@ -98,11 +270,30 @@ ngx_regex_compile(ngx_regex_compile_t *rc) char *p; pcre *re; const char *errstr; + ngx_uint_t options; ngx_regex_elt_t *elt; + options = 0; + + if (rc->options & NGX_REGEX_CASELESS) { + options |= PCRE_CASELESS; + } + + if (rc->options & NGX_REGEX_MULTILINE) { + options |= PCRE_MULTILINE; + } + + if (rc->options & ~(NGX_REGEX_CASELESS|NGX_REGEX_MULTILINE)) { + rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, + "regex \"%V\" compilation failed: invalid options", + &rc->pattern) + - rc->err.data; + return NGX_ERROR; + } + ngx_regex_malloc_init(rc->pool); - re = pcre_compile((const char *) rc->pattern.data, (int) rc->options, + re = pcre_compile((const char *) rc->pattern.data, (int) options, &errstr, &erroff, NULL); /* ensure that there is no current pool */ @@ -113,13 +304,13 @@ ngx_regex_compile(ngx_regex_compile_t *rc) rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, "pcre_compile() failed: %s in \"%V\"", errstr, &rc->pattern) - - rc->err.data; + - rc->err.data; } else { rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, "pcre_compile() failed: %s in \"%V\" at \"%s\"", errstr, &rc->pattern, rc->pattern.data + erroff) - - rc->err.data; + - rc->err.data; } return NGX_ERROR; @@ -134,8 +325,8 @@ ngx_regex_compile(ngx_regex_compile_t *rc) /* do not study at runtime */ - if (ngx_pcre_studies != NULL) { - elt = ngx_list_push(ngx_pcre_studies); + if (ngx_regex_studies != NULL) { + elt = ngx_list_push(ngx_regex_studies); if (elt == NULL) { goto nomem; } @@ -193,6 +384,83 @@ ngx_regex_compile(ngx_regex_compile_t *rc) return NGX_ERROR; } +#endif + + +#if (NGX_PCRE2) + +ngx_int_t +ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s, int *captures, ngx_uint_t size) +{ + size_t *ov; + ngx_int_t rc; + ngx_uint_t n, i; + + /* + * The pcre2_match() function might allocate memory for backtracking + * frames, typical allocations are from 40k and above. So the allocator + * is configured to do direct allocations from heap during matching. + */ + + ngx_regex_malloc_init(NULL); + + if (ngx_regex_match_data == NULL + || size > ngx_regex_match_data_size) + { + /* + * Allocate a match data if not yet allocated or smaller than + * needed. + */ + + if (ngx_regex_match_data) { + pcre2_match_data_free(ngx_regex_match_data); + } + + ngx_regex_match_data_size = size; + ngx_regex_match_data = pcre2_match_data_create(size / 3, NULL); + + if (ngx_regex_match_data == NULL) { + rc = PCRE2_ERROR_NOMEMORY; + goto failed; + } + } + + rc = pcre2_match(re, s->data, s->len, 0, 0, ngx_regex_match_data, NULL); + + if (rc < 0) { + goto failed; + } + + n = pcre2_get_ovector_count(ngx_regex_match_data); + ov = pcre2_get_ovector_pointer(ngx_regex_match_data); + + if (n > size / 3) { + n = size / 3; + } + + for (i = 0; i < n; i++) { + captures[i * 2] = ov[i * 2]; + captures[i * 2 + 1] = ov[i * 2 + 1]; + } + +failed: + + ngx_regex_malloc_done(); + + return rc; +} + +#else + +ngx_int_t +ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s, int *captures, ngx_uint_t size) +{ + return pcre_exec(re->code, re->extra, (const char *) s->data, s->len, + 0, 0, captures, size); +} + +#endif + ngx_int_t ngx_regex_exec_array(ngx_array_t *a, ngx_str_t *s, ngx_log_t *log) @@ -227,14 +495,40 @@ ngx_regex_exec_array(ngx_array_t *a, ngx_str_t *s, ngx_log_t *log) } +#if (NGX_PCRE2) + static void * ngx_libc_cdecl -ngx_regex_malloc(size_t size) +ngx_regex_malloc(size_t size, void *data) { - ngx_pool_t *pool; - pool = ngx_pcre_pool; + if (ngx_regex_pool) { + return ngx_palloc(ngx_regex_pool, size); + } - if (pool) { - return ngx_palloc(pool, size); + if (ngx_regex_direct_alloc) { + return ngx_alloc(size, ngx_cycle->log); + } + + return NULL; +} + + +static void ngx_libc_cdecl +ngx_regex_free(void *p, void *data) +{ + if (ngx_regex_direct_alloc) { + ngx_free(p); + } + + return; +} + +#else + +static void * ngx_libc_cdecl +ngx_regex_malloc(size_t size) +{ + if (ngx_regex_pool) { + return ngx_palloc(ngx_regex_pool, size); } return NULL; @@ -247,19 +541,20 @@ ngx_regex_free(void *p) return; } +#endif -#if (NGX_HAVE_PCRE_JIT) static void -ngx_pcre_free_studies(void *data) +ngx_regex_cleanup(void *data) { - ngx_list_t *studies = data; +#if (NGX_PCRE2 || NGX_HAVE_PCRE_JIT) + ngx_regex_conf_t *rcf = data; ngx_uint_t i; ngx_list_part_t *part; ngx_regex_elt_t *elts; - part = &studies->part; + part = &rcf->studies->part; elts = part->elts; for (i = 0; /* void */ ; i++) { @@ -274,56 +569,83 @@ ngx_pcre_free_studies(void *data) i = 0; } + /* + * The PCRE JIT compiler uses mmap for its executable codes, so we + * have to explicitly call the pcre_free_study() function to free + * this memory. In PCRE2, we call the pcre2_code_free() function + * for the same reason. + */ + +#if (NGX_PCRE2) + pcre2_code_free(elts[i].regex); +#else if (elts[i].regex->extra != NULL) { pcre_free_study(elts[i].regex->extra); } +#endif + } +#endif + + /* + * On configuration parsing errors ngx_regex_module_init() will not + * be called. Make sure ngx_regex_studies is properly cleared anyway. + */ + + ngx_regex_studies = NULL; + +#if (NGX_PCRE2) + + /* + * Free compile context and match data. If needed at runtime by + * the new cycle, these will be re-allocated. + */ + + if (ngx_regex_compile_context) { + pcre2_compile_context_free(ngx_regex_compile_context); + ngx_regex_compile_context = NULL; + } + + if (ngx_regex_match_data) { + pcre2_match_data_free(ngx_regex_match_data); + ngx_regex_match_data = NULL; + ngx_regex_match_data_size = 0; } -} #endif +} static ngx_int_t ngx_regex_module_init(ngx_cycle_t *cycle) { - int opt; - const char *errstr; - ngx_uint_t i; - ngx_list_part_t *part; - ngx_regex_elt_t *elts; + int opt; +#if !(NGX_PCRE2) + const char *errstr; +#endif + ngx_uint_t i; + ngx_list_part_t *part; + ngx_regex_elt_t *elts; + ngx_regex_conf_t *rcf; opt = 0; -#if (NGX_HAVE_PCRE_JIT) - { - ngx_regex_conf_t *rcf; - ngx_pool_cleanup_t *cln; - rcf = (ngx_regex_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_regex_module); +#if (NGX_PCRE2 || NGX_HAVE_PCRE_JIT) + if (rcf->pcre_jit) { +#if (NGX_PCRE2) + opt = 1; +#else opt = PCRE_STUDY_JIT_COMPILE; - - /* - * The PCRE JIT compiler uses mmap for its executable codes, so we - * have to explicitly call the pcre_free_study() function to free - * this memory. - */ - - cln = ngx_pool_cleanup_add(cycle->pool, 0); - if (cln == NULL) { - return NGX_ERROR; - } - - cln->handler = ngx_pcre_free_studies; - cln->data = ngx_pcre_studies; - } +#endif } + #endif ngx_regex_malloc_init(cycle->pool); - part = &ngx_pcre_studies->part; + part = &rcf->studies->part; elts = part->elts; for (i = 0; /* void */ ; i++) { @@ -338,6 +660,23 @@ ngx_regex_module_init(ngx_cycle_t *cycle) i = 0; } +#if (NGX_PCRE2) + + if (opt) { + int n; + + n = pcre2_jit_compile(elts[i].regex, PCRE2_JIT_COMPLETE); + + if (n != 0) { + ngx_log_error(NGX_LOG_INFO, cycle->log, 0, + "pcre2_jit_compile() failed: %d in \"%s\", " + "ignored", + n, elts[i].name); + } + } + +#else + elts[i].regex->extra = pcre_study(elts[i].regex->code, opt, &errstr); if (errstr != NULL) { @@ -360,12 +699,16 @@ ngx_regex_module_init(ngx_cycle_t *cycle) elts[i].name); } } +#endif #endif } ngx_regex_malloc_done(); - ngx_pcre_studies = NULL; + ngx_regex_studies = NULL; +#if (NGX_PCRE2) + ngx_regex_compile_context = NULL; +#endif return NGX_OK; } @@ -374,7 +717,8 @@ ngx_regex_module_init(ngx_cycle_t *cycle) static void * ngx_regex_create_conf(ngx_cycle_t *cycle) { - ngx_regex_conf_t *rcf; + ngx_regex_conf_t *rcf; + ngx_pool_cleanup_t *cln; rcf = ngx_pcalloc(cycle->pool, sizeof(ngx_regex_conf_t)); if (rcf == NULL) { @@ -383,11 +727,21 @@ ngx_regex_create_conf(ngx_cycle_t *cycle) rcf->pcre_jit = NGX_CONF_UNSET; - ngx_pcre_studies = ngx_list_create(cycle->pool, 8, sizeof(ngx_regex_elt_t)); - if (ngx_pcre_studies == NULL) { + cln = ngx_pool_cleanup_add(cycle->pool, 0); + if (cln == NULL) { return NULL; } + cln->handler = ngx_regex_cleanup; + cln->data = rcf; + + rcf->studies = ngx_list_create(cycle->pool, 8, sizeof(ngx_regex_elt_t)); + if (rcf->studies == NULL) { + return NULL; + } + + ngx_regex_studies = rcf->studies; + return rcf; } @@ -412,7 +766,21 @@ ngx_regex_pcre_jit(ngx_conf_t *cf, void *post, void *data) return NGX_CONF_OK; } -#if (NGX_HAVE_PCRE_JIT) +#if (NGX_PCRE2) + { + int r; + uint32_t jit; + + jit = 0; + r = pcre2_config(PCRE2_CONFIG_JIT, &jit); + + if (r != 0 || jit != 1) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "PCRE2 library does not support JIT"); + *fp = 0; + } + } +#elif (NGX_HAVE_PCRE_JIT) { int jit, r; diff --git a/src/core/ngx_regex.h b/src/core/ngx_regex.h index 680486c816..182373a22b 100644 --- a/src/core/ngx_regex.h +++ b/src/core/ngx_regex.h @@ -12,24 +12,38 @@ #include #include -#include +#if (NGX_PCRE2) + +#define PCRE2_CODE_UNIT_WIDTH 8 +#include + +#define NGX_REGEX_NO_MATCHED PCRE2_ERROR_NOMATCH /* -1 */ -#define NGX_REGEX_NO_MATCHED PCRE_ERROR_NOMATCH /* -1 */ +typedef pcre2_code ngx_regex_t; -#define NGX_REGEX_CASELESS PCRE_CASELESS +#else + +#include +#define NGX_REGEX_NO_MATCHED PCRE_ERROR_NOMATCH /* -1 */ typedef struct { pcre *code; pcre_extra *extra; } ngx_regex_t; +#endif + + +#define NGX_REGEX_CASELESS 0x00000001 +#define NGX_REGEX_MULTILINE 0x00000002 + typedef struct { ngx_str_t pattern; ngx_pool_t *pool; - ngx_int_t options; + ngx_uint_t options; ngx_regex_t *regex; int captures; @@ -49,10 +63,14 @@ typedef struct { void ngx_regex_init(void); ngx_int_t ngx_regex_compile(ngx_regex_compile_t *rc); -#define ngx_regex_exec(re, s, captures, size) \ - pcre_exec(re->code, re->extra, (const char *) (s)->data, (s)->len, 0, 0, \ - captures, size) -#define ngx_regex_exec_n "pcre_exec()" +ngx_int_t ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s, int *captures, + ngx_uint_t size); + +#if (NGX_PCRE2) +#define ngx_regex_exec_n "pcre2_match()" +#else +#define ngx_regex_exec_n "pcre_exec()" +#endif ngx_int_t ngx_regex_exec_array(ngx_array_t *a, ngx_str_t *s, ngx_log_t *log); diff --git a/src/core/ngx_resolver.c b/src/core/ngx_resolver.c index b2af8eeb90..e432fd20af 100644 --- a/src/core/ngx_resolver.c +++ b/src/core/ngx_resolver.c @@ -51,9 +51,7 @@ typedef struct { } ngx_resolver_an_t; -#define ngx_resolver_node(n) \ - (ngx_resolver_node_t *) \ - ((u_char *) (n) - offsetof(ngx_resolver_node_t, node)) +#define ngx_resolver_node(n) ngx_rbtree_data(n, ngx_resolver_node_t, node) static ngx_int_t ngx_udp_connect(ngx_resolver_connection_t *rec); @@ -1719,13 +1717,28 @@ ngx_resolver_udp_read(ngx_event_t *rev) do { n = ngx_udp_recv(c, buf, NGX_RESOLVER_UDP_SIZE); - if (n < 0) { - return; + if (n == NGX_AGAIN) { + break; + } + + if (n == NGX_ERROR) { + goto failed; } ngx_resolver_process_response(rec->resolver, buf, n, 0); } while (rev->ready); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + goto failed; + } + + return; + +failed: + + ngx_close_connection(rec->udp); + rec->udp = NULL; } @@ -1939,6 +1952,12 @@ ngx_resolver_process_response(ngx_resolver_t *r, u_char *buf, size_t n, i = sizeof(ngx_resolver_hdr_t); while (i < (ngx_uint_t) n) { + + if (buf[i] & 0xc0) { + err = "unexpected compression pointer in DNS response"; + goto done; + } + if (buf[i] == '\0') { goto found; } @@ -2074,7 +2093,7 @@ ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t n, if (rn == NULL) { ngx_log_error(r->log_level, r->log, 0, - "unexpected response for %V", &name); + "unexpected DNS response for %V", &name); ngx_resolver_free(r, name.data); goto failed; } @@ -2086,7 +2105,7 @@ ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t n, if (rn->query6 == NULL || rn->naddrs6 != (u_short) -1) { ngx_log_error(r->log_level, r->log, 0, - "unexpected response for %V", &name); + "unexpected DNS response for %V", &name); ngx_resolver_free(r, name.data); goto failed; } @@ -2105,7 +2124,7 @@ ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t n, if (rn->query == NULL || rn->naddrs != (u_short) -1) { ngx_log_error(r->log_level, r->log, 0, - "unexpected response for %V", &name); + "unexpected DNS response for %V", &name); ngx_resolver_free(r, name.data); goto failed; } @@ -2120,7 +2139,7 @@ ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t n, if (ident != qident) { ngx_log_error(r->log_level, r->log, 0, - "wrong ident %ui response for %V, expect %ui", + "wrong ident %ui in DNS response for %V, expect %ui", ident, &name, qident); ngx_resolver_free(r, name.data); goto failed; @@ -2305,7 +2324,7 @@ ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t n, if (class != 1) { ngx_log_error(r->log_level, r->log, 0, - "unexpected RR class %ui", class); + "unexpected RR class %ui in DNS response", class); goto failed; } @@ -2374,7 +2393,7 @@ ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t n, default: ngx_log_error(r->log_level, r->log, 0, - "unexpected RR type %ui", type); + "unexpected RR type %ui in DNS response", type); } i += len; @@ -2723,7 +2742,7 @@ ngx_resolver_process_srv(ngx_resolver_t *r, u_char *buf, size_t n, if (rn == NULL || rn->query == NULL) { ngx_log_error(r->log_level, r->log, 0, - "unexpected response for %V", &name); + "unexpected DNS response for %V", &name); ngx_resolver_free(r, name.data); goto failed; } @@ -2737,7 +2756,7 @@ ngx_resolver_process_srv(ngx_resolver_t *r, u_char *buf, size_t n, if (ident != qident) { ngx_log_error(r->log_level, r->log, 0, - "wrong ident %ui response for %V, expect %ui", + "wrong ident %ui in DNS response for %V, expect %ui", ident, &name, qident); ngx_resolver_free(r, name.data); goto failed; @@ -2847,7 +2866,7 @@ ngx_resolver_process_srv(ngx_resolver_t *r, u_char *buf, size_t n, if (class != 1) { ngx_log_error(r->log_level, r->log, 0, - "unexpected RR class %ui", class); + "unexpected RR class %ui in DNS response", class); goto failed; } @@ -2890,7 +2909,7 @@ ngx_resolver_process_srv(ngx_resolver_t *r, u_char *buf, size_t n, default: ngx_log_error(r->log_level, r->log, 0, - "unexpected RR type %ui", type); + "unexpected RR type %ui in DNS response", type); } i += len; @@ -3321,7 +3340,7 @@ ngx_resolver_process_ptr(ngx_resolver_t *r, u_char *buf, size_t n, if (rn == NULL || rn->query == NULL) { ngx_log_error(r->log_level, r->log, 0, - "unexpected response for %V", &name); + "unexpected DNS response for %V", &name); ngx_resolver_free(r, name.data); goto failed; } @@ -3330,7 +3349,7 @@ ngx_resolver_process_ptr(ngx_resolver_t *r, u_char *buf, size_t n, if (ident != qident) { ngx_log_error(r->log_level, r->log, 0, - "wrong ident %ui response for %V, expect %ui", + "wrong ident %ui in DNS response for %V, expect %ui", ident, &name, qident); ngx_resolver_free(r, name.data); goto failed; @@ -3412,7 +3431,7 @@ ngx_resolver_process_ptr(ngx_resolver_t *r, u_char *buf, size_t n, if (class != 1) { ngx_log_error(r->log_level, r->log, 0, - "unexpected RR class %ui", class); + "unexpected RR class %ui in DNS response", class); goto failed; } @@ -3439,7 +3458,7 @@ ngx_resolver_process_ptr(ngx_resolver_t *r, u_char *buf, size_t n, default: ngx_log_error(r->log_level, r->log, 0, - "unexpected RR type %ui", type); + "unexpected RR type %ui in DNS response", type); } i += len; @@ -4080,11 +4099,11 @@ ngx_resolver_copy(ngx_resolver_t *r, ngx_str_t *name, u_char *buf, u_char *src, { char *err; u_char *p, *dst; - ssize_t len; + size_t len; ngx_uint_t i, n; p = src; - len = -1; + len = 0; /* * compression pointers allow to create endless loop, so we set limit; @@ -4099,6 +4118,16 @@ ngx_resolver_copy(ngx_resolver_t *r, ngx_str_t *name, u_char *buf, u_char *src, } if (n & 0xc0) { + if ((n & 0xc0) != 0xc0) { + err = "invalid label type in DNS response"; + goto invalid; + } + + if (p >= last) { + err = "name is out of DNS response"; + goto invalid; + } + n = ((n & 0x3f) << 8) + *p; p = &buf[n]; @@ -4108,12 +4137,12 @@ ngx_resolver_copy(ngx_resolver_t *r, ngx_str_t *name, u_char *buf, u_char *src, } if (p >= last) { - err = "name is out of response"; + err = "name is out of DNS response"; goto invalid; } } - err = "compression pointers loop"; + err = "compression pointers loop in DNS response"; invalid: @@ -4127,7 +4156,7 @@ ngx_resolver_copy(ngx_resolver_t *r, ngx_str_t *name, u_char *buf, u_char *src, return NGX_OK; } - if (len == -1) { + if (len == 0) { ngx_str_null(name); return NGX_OK; } @@ -4139,30 +4168,23 @@ ngx_resolver_copy(ngx_resolver_t *r, ngx_str_t *name, u_char *buf, u_char *src, name->data = dst; - n = *src++; - for ( ;; ) { + n = *src++; + + if (n == 0) { + name->len = dst - name->data - 1; + return NGX_OK; + } + if (n & 0xc0) { n = ((n & 0x3f) << 8) + *src; src = &buf[n]; - n = *src++; - } else { - if (dst != name->data) { - *dst++ = '.'; - } - ngx_strlow(dst, src, n); dst += n; src += n; - - n = *src++; - } - - if (n == 0) { - name->len = dst - name->data; - return NGX_OK; + *dst++ = '.'; } } } @@ -4600,6 +4622,8 @@ ngx_udp_connect(ngx_resolver_connection_t *rec) c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + c->start_time = ngx_current_msec; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, &rec->log, 0, "connect to %V, fd:%d #%uA", &rec->server, s, c->number); @@ -4686,6 +4710,8 @@ ngx_tcp_connect(ngx_resolver_connection_t *rec) c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + c->start_time = ngx_current_msec; + if (ngx_add_conn) { if (ngx_add_conn(c) == NGX_ERROR) { goto failed; diff --git a/src/core/ngx_rwlock.c b/src/core/ngx_rwlock.c index ed2b0f8105..e7da8a8ec5 100644 --- a/src/core/ngx_rwlock.c +++ b/src/core/ngx_rwlock.c @@ -89,22 +89,10 @@ ngx_rwlock_rlock(ngx_atomic_t *lock) void ngx_rwlock_unlock(ngx_atomic_t *lock) { - ngx_atomic_uint_t readers; - - readers = *lock; - - if (readers == NGX_RWLOCK_WLOCK) { + if (*lock == NGX_RWLOCK_WLOCK) { (void) ngx_atomic_cmp_set(lock, NGX_RWLOCK_WLOCK, 0); - return; - } - - for ( ;; ) { - - if (ngx_atomic_cmp_set(lock, readers, readers - 1)) { - return; - } - - readers = *lock; + } else { + (void) ngx_atomic_fetch_add(lock, -1); } } diff --git a/src/core/ngx_string.c b/src/core/ngx_string.c index 928ca9fe6a..af46855ddd 100644 --- a/src/core/ngx_string.c +++ b/src/core/ngx_string.c @@ -11,6 +11,8 @@ static u_char *ngx_sprintf_num(u_char *buf, u_char *last, uint64_t ui64, u_char zero, ngx_uint_t hexadecimal, ngx_uint_t width); +static u_char *ngx_sprintf_str(u_char *buf, u_char *last, u_char *src, + size_t len, ngx_uint_t hexadecimal); static void ngx_encode_base64_internal(ngx_str_t *dst, ngx_str_t *src, const u_char *basis, ngx_uint_t padding); static ngx_int_t ngx_decode_base64_internal(ngx_str_t *dst, ngx_str_t *src, @@ -101,10 +103,10 @@ ngx_pstrdup(ngx_pool_t *pool, ngx_str_t *src) * %M ngx_msec_t * %r rlim_t * %p void * - * %V ngx_str_t * - * %v ngx_variable_value_t * - * %s null-terminated string - * %*s length and string + * %[x|X]V ngx_str_t * + * %[x|X]v ngx_variable_value_t * + * %[x|X]s null-terminated string + * %*[x|X]s length and string * %Z '\0' * %N '\n' * %c char @@ -165,7 +167,7 @@ ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args) u_char *p, zero; int d; double f; - size_t len, slen; + size_t slen; int64_t i64; uint64_t ui64, frac; ngx_msec_t ms; @@ -250,8 +252,7 @@ ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args) case 'V': v = va_arg(args, ngx_str_t *); - len = ngx_min(((size_t) (last - buf)), v->len); - buf = ngx_cpymem(buf, v->data, len); + buf = ngx_sprintf_str(buf, last, v->data, v->len, hex); fmt++; continue; @@ -259,8 +260,7 @@ ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args) case 'v': vv = va_arg(args, ngx_variable_value_t *); - len = ngx_min(((size_t) (last - buf)), vv->len); - buf = ngx_cpymem(buf, vv->data, len); + buf = ngx_sprintf_str(buf, last, vv->data, vv->len, hex); fmt++; continue; @@ -268,16 +268,7 @@ ngx_vslprintf(u_char *buf, u_char *last, const char *fmt, va_list args) case 's': p = va_arg(args, u_char *); - if (slen == (size_t) -1) { - while (*p && buf < last) { - *buf++ = *p++; - } - - } else { - len = ngx_min(((size_t) (last - buf)), slen); - buf = ngx_cpymem(buf, p, len); - } - + buf = ngx_sprintf_str(buf, last, p, slen, hex); fmt++; continue; @@ -576,6 +567,64 @@ ngx_sprintf_num(u_char *buf, u_char *last, uint64_t ui64, u_char zero, } +static u_char * +ngx_sprintf_str(u_char *buf, u_char *last, u_char *src, size_t len, + ngx_uint_t hexadecimal) +{ + static u_char hex[] = "0123456789abcdef"; + static u_char HEX[] = "0123456789ABCDEF"; + + if (hexadecimal == 0) { + + if (len == (size_t) -1) { + while (*src && buf < last) { + *buf++ = *src++; + } + + } else { + len = ngx_min((size_t) (last - buf), len); + buf = ngx_cpymem(buf, src, len); + } + + } else if (hexadecimal == 1) { + + if (len == (size_t) -1) { + + while (*src && buf < last - 1) { + *buf++ = hex[*src >> 4]; + *buf++ = hex[*src++ & 0xf]; + } + + } else { + + while (len-- && buf < last - 1) { + *buf++ = hex[*src >> 4]; + *buf++ = hex[*src++ & 0xf]; + } + } + + } else { /* hexadecimal == 2 */ + + if (len == (size_t) -1) { + + while (*src && buf < last - 1) { + *buf++ = HEX[*src >> 4]; + *buf++ = HEX[*src++ & 0xf]; + } + + } else { + + while (len-- && buf < last - 1) { + *buf++ = HEX[*src >> 4]; + *buf++ = HEX[*src++ & 0xf]; + } + } + } + + return buf; +} + + /* * We use ngx_strcasecmp()/ngx_strncasecmp() for 7-bit ASCII strings only, * and implement our own ngx_strcasecmp()/ngx_strncasecmp() @@ -1472,19 +1521,32 @@ ngx_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type) uint32_t *escape; static u_char hex[] = "0123456789ABCDEF"; - /* " ", "#", "%", "?", %00-%1F, %7F-%FF */ + /* + * Per RFC 3986 only the following chars are allowed in URIs unescaped: + * + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + * + * And "%" can appear as a part of escaping itself. The following + * characters are not allowed and need to be escaped: %00-%1F, %7F-%FF, + * " ", """, "<", ">", "\", "^", "`", "{", "|", "}". + */ + + /* " ", "#", "%", "?", not allowed */ static uint32_t uri[] = { 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ - 0x80000029, /* 1000 0000 0000 0000 0000 0000 0010 1001 */ + 0xd000002d, /* 1101 0000 0000 0000 0000 0000 0010 1101 */ /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ - 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x50000000, /* 0101 0000 0000 0000 0000 0000 0000 0000 */ /* ~}| {zyx wvut srqp onml kjih gfed cba` */ - 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ + 0xb8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ @@ -1492,19 +1554,19 @@ ngx_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type) 0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */ }; - /* " ", "#", "%", "&", "+", "?", %00-%1F, %7F-%FF */ + /* " ", "#", "%", "&", "+", ";", "?", not allowed */ static uint32_t args[] = { 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ - 0x88000869, /* 1000 1000 0000 0000 0000 1000 0110 1001 */ + 0xd800086d, /* 1101 1000 0000 0000 0000 1000 0110 1101 */ /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ - 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x50000000, /* 0101 0000 0000 0000 0000 0000 0000 0000 */ /* ~}| {zyx wvut srqp onml kjih gfed cba` */ - 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ + 0xb8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ @@ -1532,19 +1594,19 @@ ngx_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type) 0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */ }; - /* " ", "#", """, "%", "'", %00-%1F, %7F-%FF */ + /* " ", "#", """, "%", "'", not allowed */ static uint32_t html[] = { 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ - 0x000000ad, /* 0000 0000 0000 0000 0000 0000 1010 1101 */ + 0x500000ad, /* 0101 0000 0000 0000 0000 0000 1010 1101 */ /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ - 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x50000000, /* 0101 0000 0000 0000 0000 0000 0000 0000 */ /* ~}| {zyx wvut srqp onml kjih gfed cba` */ - 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ + 0xb8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ @@ -1552,19 +1614,19 @@ ngx_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type) 0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */ }; - /* " ", """, "%", "'", %00-%1F, %7F-%FF */ + /* " ", """, "'", not allowed */ static uint32_t refresh[] = { 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ - 0x00000085, /* 0000 0000 0000 0000 0000 0000 1000 0101 */ + 0x50000085, /* 0101 0000 0000 0000 0000 0000 1000 0101 */ /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ - 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + 0x50000000, /* 0101 0000 0000 0000 0000 0000 0000 0000 */ /* ~}| {zyx wvut srqp onml kjih gfed cba` */ - 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ + 0xd8000001, /* 1011 1000 0000 0000 0000 0000 0000 0001 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ diff --git a/src/core/ngx_times.c b/src/core/ngx_times.c index 55af267415..3a1be32f9b 100644 --- a/src/core/ngx_times.c +++ b/src/core/ngx_times.c @@ -227,10 +227,6 @@ ngx_monotonic_time(time_t sec, ngx_uint_t msec) #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 diff --git a/src/event/modules/ngx_eventport_module.c b/src/event/modules/ngx_eventport_module.c index 0a88fc250c..f7ad6092ba 100644 --- a/src/event/modules/ngx_eventport_module.c +++ b/src/event/modules/ngx_eventport_module.c @@ -403,7 +403,7 @@ ngx_eventport_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags) return NGX_ERROR; } - } else { + } else if (ev->active) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, "eventport del event: fd:%d", c->fd); diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c index 0c98898201..3d784011a9 100644 --- a/src/event/ngx_event.c +++ b/src/event/ngx_event.c @@ -60,6 +60,7 @@ ngx_uint_t ngx_accept_events; ngx_uint_t ngx_accept_mutex_held; ngx_msec_t ngx_accept_mutex_delay; ngx_int_t ngx_accept_disabled; +ngx_uint_t ngx_use_exclusive_accept; #if (NGX_STAT_STUB) @@ -270,9 +271,7 @@ ngx_process_events_and_timers(ngx_cycle_t *cycle) ngx_shmtx_unlock(&ngx_accept_mutex); } - if (delta) { - ngx_event_expire_timers(); - } + ngx_event_expire_timers(); ngx_event_process_posted(cycle, &ngx_posted_events); } @@ -331,7 +330,7 @@ ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) return NGX_OK; } - if (rev->oneshot && !rev->ready) { + if (rev->oneshot && rev->ready) { if (ngx_del_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { return NGX_ERROR; } @@ -456,20 +455,23 @@ ngx_event_init_conf(ngx_cycle_t *cycle, void *conf) #if (NGX_HAVE_REUSEPORT) - ls = cycle->listening.elts; - for (i = 0; i < cycle->listening.nelts; i++) { + if (!ngx_test_config) { - if (!ls[i].reuseport || ls[i].worker != 0) { - continue; - } + ls = cycle->listening.elts; + for (i = 0; i < cycle->listening.nelts; i++) { - if (ngx_clone_listening(cycle, &ls[i]) != NGX_OK) { - return NGX_CONF_ERROR; - } + if (!ls[i].reuseport || ls[i].worker != 0) { + continue; + } - /* cloning may change cycle->listening.elts */ + if (ngx_clone_listening(cycle, &ls[i]) != NGX_OK) { + return NGX_CONF_ERROR; + } - ls = cycle->listening.elts; + /* cloning may change cycle->listening.elts */ + + ls = cycle->listening.elts; + } } #endif @@ -667,6 +669,8 @@ ngx_event_process_init(ngx_cycle_t *cycle) #endif + ngx_use_exclusive_accept = 0; + ngx_queue_init(&ngx_posted_accept_events); ngx_queue_init(&ngx_posted_next_events); ngx_queue_init(&ngx_posted_events); @@ -930,6 +934,8 @@ ngx_event_process_init(ngx_cycle_t *cycle) if ((ngx_event_flags & NGX_USE_EPOLL_EVENT) && ccf->worker_processes > 1) { + ngx_use_exclusive_accept = 1; + if (ngx_add_event(rev, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT) == NGX_ERROR) { diff --git a/src/event/ngx_event.h b/src/event/ngx_event.h index ce30cd18a5..1b32cf2896 100644 --- a/src/event/ngx_event.h +++ b/src/event/ngx_event.h @@ -155,10 +155,6 @@ struct ngx_event_aio_s { ngx_fd_t fd; -#if (NGX_HAVE_AIO_SENDFILE || NGX_COMPAT) - ssize_t (*preload_handler)(ngx_buf_t *file); -#endif - #if (NGX_HAVE_EVENTFD) int64_t res; #endif @@ -206,7 +202,6 @@ extern ngx_uint_t ngx_use_epoll_rdhup; #endif #if (T_NGX_ACCEPT_FILTER) typedef ngx_int_t (*ngx_event_accept_filter_pt) (ngx_connection_t *c); -void ngx_close_accepted_connection(ngx_connection_t *c); extern ngx_event_accept_filter_pt ngx_event_top_accept_filter; #endif @@ -489,6 +484,7 @@ extern ngx_uint_t ngx_accept_events; extern ngx_uint_t ngx_accept_mutex_held; extern ngx_msec_t ngx_accept_mutex_delay; extern ngx_int_t ngx_accept_disabled; +extern ngx_uint_t ngx_use_exclusive_accept; #if (NGX_STAT_STUB) diff --git a/src/event/ngx_event_accept.c b/src/event/ngx_event_accept.c index 7e749f52f3..93f13e1b4c 100644 --- a/src/event/ngx_event_accept.c +++ b/src/event/ngx_event_accept.c @@ -11,9 +11,10 @@ static ngx_int_t ngx_disable_accept_events(ngx_cycle_t *cycle, ngx_uint_t all); -#if (!T_NGX_ACCEPT_FILTER) -static void ngx_close_accepted_connection(ngx_connection_t *c); +#if (NGX_HAVE_EPOLLEXCLUSIVE) +static void ngx_reorder_accept_events(ngx_listening_t *ls); #endif +static void ngx_close_accepted_connection(ngx_connection_t *c); void @@ -261,6 +262,8 @@ ngx_event_accept(ngx_event_t *ev) c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + c->start_time = ngx_current_msec; + #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_handled, 1); #endif @@ -333,6 +336,10 @@ ngx_event_accept(ngx_event_t *ev) } } while (ev->available); + +#if (NGX_HAVE_EPOLLEXCLUSIVE) + ngx_reorder_accept_events(ls); +#endif } @@ -447,10 +454,58 @@ ngx_disable_accept_events(ngx_cycle_t *cycle, ngx_uint_t all) } -#if (!T_NGX_ACCEPT_FILTER) -static +#if (NGX_HAVE_EPOLLEXCLUSIVE) + +static void +ngx_reorder_accept_events(ngx_listening_t *ls) +{ + ngx_connection_t *c; + + /* + * Linux with EPOLLEXCLUSIVE usually notifies only the process which + * was first to add the listening socket to the epoll instance. As + * a result most of the connections are handled by the first worker + * process. To fix this, we re-add the socket periodically, so other + * workers will get a chance to accept connections. + */ + + if (!ngx_use_exclusive_accept) { + return; + } + +#if (NGX_HAVE_REUSEPORT) + + if (ls->reuseport) { + return; + } + #endif -void + + c = ls->connection; + + if (c->requests++ % 16 != 0 + && ngx_accept_disabled <= 0) + { + return; + } + + if (ngx_del_event(c->read, NGX_READ_EVENT, NGX_DISABLE_EVENT) + == NGX_ERROR) + { + return; + } + + if (ngx_add_event(c->read, NGX_READ_EVENT, NGX_EXCLUSIVE_EVENT) + == NGX_ERROR) + { + return; + } +} + +#endif + + +static void ngx_close_accepted_connection(ngx_connection_t *c) { ngx_socket_t fd; diff --git a/src/event/ngx_event_acceptex.c b/src/event/ngx_event_acceptex.c index 1999faf209..f4a1c4b2b9 100644 --- a/src/event/ngx_event_acceptex.c +++ b/src/event/ngx_event_acceptex.c @@ -80,6 +80,8 @@ ngx_event_acceptex(ngx_event_t *rev) c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + c->start_time = ngx_current_msec; + ls->handler(c); return; diff --git a/src/event/ngx_event_connect.c b/src/event/ngx_event_connect.c index 1ffa7984cf..adbbde6336 100644 --- a/src/event/ngx_event_connect.c +++ b/src/event/ngx_event_connect.c @@ -193,6 +193,8 @@ ngx_event_connect_peer(ngx_peer_connection_t *pc) c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + c->start_time = ngx_current_msec; + if (ngx_add_conn) { if (ngx_add_conn(c) == NGX_ERROR) { goto failed; diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 01ff607d3b..e13cfaa689 100755 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -47,6 +47,8 @@ static void ngx_ssl_write_handler(ngx_event_t *wev); static ssize_t ngx_ssl_write_early(ngx_connection_t *c, u_char *data, size_t size); #endif +static ssize_t ngx_ssl_sendfile(ngx_connection_t *c, ngx_buf_t *file, + size_t size); static void ngx_ssl_read_handler(ngx_event_t *rev); static void ngx_ssl_shutdown_handler(ngx_event_t *ev); static void ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, @@ -83,7 +85,7 @@ static time_t ngx_ssl_parse_time( #if OPENSSL_VERSION_NUMBER > 0x10100000L const #endif - ASN1_TIME *asn1time); + ASN1_TIME *asn1time, ngx_log_t *log); static void *ngx_openssl_create_conf(ngx_cycle_t *cycle); static char *ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -155,6 +157,7 @@ int ngx_ssl_connection_index; int ngx_ssl_server_conf_index; int ngx_ssl_session_cache_index; int ngx_ssl_session_ticket_keys_index; +int ngx_ssl_ocsp_index; int ngx_ssl_certificate_index; int ngx_ssl_next_certificate_index; int ngx_ssl_certificate_name_index; @@ -407,6 +410,13 @@ ngx_ssl_init(ngx_log_t *log) return NGX_ERROR; } + ngx_ssl_ocsp_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); + if (ngx_ssl_ocsp_index == -1) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, + "SSL_CTX_get_ex_new_index() failed"); + return NGX_ERROR; + } + ngx_ssl_certificate_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); if (ngx_ssl_certificate_index == -1) { @@ -537,11 +547,6 @@ ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data) SSL_CTX_set_options(ssl->ctx, SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER); #endif -#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING - /* this option allow a potential SSL 2.0 rollback (CAN-2005-2969) */ - SSL_CTX_set_options(ssl->ctx, SSL_OP_MSIE_SSLV2_RSA_PADDING); -#endif - #ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG SSL_CTX_set_options(ssl->ctx, SSL_OP_SSLEAY_080_CLIENT_DH_BUG); #endif @@ -616,6 +621,10 @@ ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data) SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_CLIENT_RENEGOTIATION); #endif +#ifdef SSL_OP_IGNORE_UNEXPECTED_EOF + SSL_CTX_set_options(ssl->ctx, SSL_OP_IGNORE_UNEXPECTED_EOF); +#endif + #ifdef SSL_MODE_RELEASE_BUFFERS SSL_CTX_set_mode(ssl->ctx, SSL_MODE_RELEASE_BUFFERS); #endif @@ -1162,11 +1171,6 @@ ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers, SSL_CTX_set_options(ssl->ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); } -#if (OPENSSL_VERSION_NUMBER < 0x10100001L && !defined LIBRESSL_VERSION_NUMBER) - /* a temporary 512-bit RSA key is required for export versions of MSIE */ - SSL_CTX_set_tmp_rsa_callback(ssl->ctx, ngx_ssl_rsa512_key_callback); -#endif - return NGX_OK; } @@ -1223,6 +1227,9 @@ ngx_int_t ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, ngx_int_t depth) { + SSL_CTX_set_verify(ssl->ctx, SSL_CTX_get_verify_mode(ssl->ctx), + ngx_ssl_verify_callback); + SSL_CTX_set_verify_depth(ssl->ctx, depth); if (cert->len == 0) { @@ -1314,26 +1321,52 @@ ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store) c = ngx_ssl_get_connection(ssl_conn); + if (!(c->log->log_level & NGX_LOG_DEBUG_EVENT)) { + return 1; + } + cert = X509_STORE_CTX_get_current_cert(x509_store); err = X509_STORE_CTX_get_error(x509_store); depth = X509_STORE_CTX_get_error_depth(x509_store); sname = X509_get_subject_name(cert); - subject = sname ? X509_NAME_oneline(sname, NULL, 0) : "(none)"; + + if (sname) { + subject = X509_NAME_oneline(sname, NULL, 0); + if (subject == NULL) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, + "X509_NAME_oneline() failed"); + } + + } else { + subject = NULL; + } iname = X509_get_issuer_name(cert); - issuer = iname ? X509_NAME_oneline(iname, NULL, 0) : "(none)"; + + if (iname) { + issuer = X509_NAME_oneline(iname, NULL, 0); + if (issuer == NULL) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, + "X509_NAME_oneline() failed"); + } + + } else { + issuer = NULL; + } ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, "verify:%d, error:%d, depth:%d, " "subject:\"%s\", issuer:\"%s\"", - ok, err, depth, subject, issuer); + ok, err, depth, + subject ? subject : "(none)", + issuer ? issuer : "(none)"); - if (sname) { + if (subject) { OPENSSL_free(subject); } - if (iname) { + if (issuer) { OPENSSL_free(issuer); } #endif @@ -1390,28 +1423,6 @@ ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, int ret) } -RSA * -ngx_ssl_rsa512_key_callback(ngx_ssl_conn_t *ssl_conn, int is_export, - int key_length) -{ - static RSA *key; - - if (key_length != 512) { - return NULL; - } - -#if (OPENSSL_VERSION_NUMBER < 0x10100003L && !defined OPENSSL_NO_DEPRECATED) - - if (key == NULL) { - key = RSA_generate_key(512, RSA_F4, NULL, NULL); - } - -#endif - - return key; -} - - ngx_array_t * ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file) { @@ -1624,7 +1635,6 @@ ngx_ssl_passwords_cleanup(void *data) ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file) { - DH *dh; BIO *bio; if (file->len == 0) { @@ -1642,6 +1652,10 @@ ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file) return NGX_ERROR; } +#ifdef SSL_CTX_set_tmp_dh + { + DH *dh; + dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); if (dh == NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, @@ -1650,9 +1664,45 @@ ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file) return NGX_ERROR; } - SSL_CTX_set_tmp_dh(ssl->ctx, dh); + if (SSL_CTX_set_tmp_dh(ssl->ctx, dh) != 1) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set_tmp_dh(\"%s\") failed", file->data); + DH_free(dh); + BIO_free(bio); + return NGX_ERROR; + } DH_free(dh); + } +#else + { + EVP_PKEY *dh; + + /* + * PEM_read_bio_DHparams() and SSL_CTX_set_tmp_dh() + * are deprecated in OpenSSL 3.0 + */ + + dh = PEM_read_bio_Parameters(bio, NULL); + if (dh == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "PEM_read_bio_Parameters(\"%s\") failed", file->data); + BIO_free(bio); + return NGX_ERROR; + } + + if (SSL_CTX_set0_tmp_dh_pkey(ssl->ctx, dh) != 1) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set0_tmp_dh_pkey(\%s\") failed", file->data); +#if (OPENSSL_VERSION_NUMBER >= 0x3000001fL) + EVP_PKEY_free(dh); +#endif + BIO_free(bio); + return NGX_ERROR; + } + } +#endif + BIO_free(bio); return NGX_OK; @@ -1770,6 +1820,78 @@ ngx_ssl_early_data(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable) } +ngx_int_t +ngx_ssl_conf_commands(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *commands) +{ + if (commands == NULL) { + return NGX_OK; + } + +#ifdef SSL_CONF_FLAG_FILE + { + int type; + u_char *key, *value; + ngx_uint_t i; + ngx_keyval_t *cmd; + SSL_CONF_CTX *cctx; + + cctx = SSL_CONF_CTX_new(); + if (cctx == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CONF_CTX_new() failed"); + return NGX_ERROR; + } + + SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE); + SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SERVER); + SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CLIENT); + SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CERTIFICATE); + SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SHOW_ERRORS); + + SSL_CONF_CTX_set_ssl_ctx(cctx, ssl->ctx); + + cmd = commands->elts; + for (i = 0; i < commands->nelts; i++) { + + key = cmd[i].key.data; + type = SSL_CONF_cmd_value_type(cctx, (char *) key); + + if (type == SSL_CONF_TYPE_FILE || type == SSL_CONF_TYPE_DIR) { + if (ngx_conf_full_name(cf->cycle, &cmd[i].value, 1) != NGX_OK) { + SSL_CONF_CTX_free(cctx); + return NGX_ERROR; + } + } + + value = cmd[i].value.data; + + if (SSL_CONF_cmd(cctx, (char *) key, (char *) value) <= 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CONF_cmd(\"%s\", \"%s\") failed", key, value); + SSL_CONF_CTX_free(cctx); + return NGX_ERROR; + } + } + + if (SSL_CONF_CTX_finish(cctx) != 1) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CONF_finish() failed"); + SSL_CONF_CTX_free(cctx); + return NGX_ERROR; + } + + SSL_CONF_CTX_free(cctx); + + return NGX_OK; + } +#else + ngx_log_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CONF_cmd() is not available on this platform"); + return NGX_ERROR; +#endif +} + + ngx_int_t ngx_ssl_client_session_cache(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable) { @@ -1908,6 +2030,7 @@ ngx_ssl_handshake(ngx_connection_t *c) { int n, sslerr; ngx_err_t err; + ngx_int_t rc; #ifdef SSL_READ_EARLY_DATA_SUCCESS if (c->ssl->try_early_data) { @@ -1916,7 +2039,6 @@ ngx_ssl_handshake(ngx_connection_t *c) #endif #if (T_NGX_HAVE_DTLS) - ngx_int_t rc; struct timeval timeout; if (c->type == SOCK_DGRAM @@ -1930,6 +2052,10 @@ ngx_ssl_handshake(ngx_connection_t *c) } #endif + if (c->ssl->in_ocsp) { + return ngx_ssl_ocsp_validate(c); + } + ngx_ssl_clear_error(c->log); n = SSL_do_handshake(c->ssl->connection); @@ -1962,9 +2088,6 @@ ngx_ssl_handshake(ngx_connection_t *c) #if (NGX_DEBUG) ngx_ssl_handshake_log(c); #endif - - c->ssl->handshaked = 1; - #if (T_NGX_SSL_HANDSHAKE_TIME) ngx_time_t *tp; tp = ngx_timeofday(); @@ -1976,6 +2099,9 @@ ngx_ssl_handshake(ngx_connection_t *c) c->recv_chain = ngx_ssl_recv_chain; c->send_chain = ngx_ssl_send_chain; + c->read->ready = 1; + c->write->ready = 1; + #ifndef SSL_OP_NO_RENEGOTIATION #if OPENSSL_VERSION_NUMBER < 0x10100000L #ifdef SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS @@ -1989,6 +2115,30 @@ ngx_ssl_handshake(ngx_connection_t *c) #endif #endif +#ifdef BIO_get_ktls_send + + if (BIO_get_ktls_send(SSL_get_wbio(c->ssl->connection)) == 1) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "BIO_get_ktls_send(): 1"); + c->ssl->sendfile = 1; + } + +#endif + + rc = ngx_ssl_ocsp_validate(c); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + c->read->handler = ngx_ssl_handshake_handler; + c->write->handler = ngx_ssl_handshake_handler; + return NGX_AGAIN; + } + + c->ssl->handshaked = 1; + return NGX_OK; } @@ -2101,6 +2251,13 @@ ngx_ssl_handshake(ngx_connection_t *c) return NGX_ERROR; } + if (c->ssl->handshake_rejected) { + ngx_connection_error(c, err, "handshake rejected"); + ERR_clear_error(); + + return NGX_ERROR; + } + c->read->error = 1; ngx_ssl_connection_error(c, sslerr, err, "SSL_do_handshake() failed"); @@ -2118,6 +2275,7 @@ ngx_ssl_try_early_data(ngx_connection_t *c) u_char buf; size_t readbytes; ngx_err_t err; + ngx_int_t rc; ngx_ssl_clear_error(c->log); @@ -2152,7 +2310,6 @@ ngx_ssl_try_early_data(ngx_connection_t *c) c->ssl->early_buf = buf; c->ssl->early_preread = 1; - c->ssl->handshaked = 1; c->ssl->in_early = 1; c->recv = ngx_ssl_recv; @@ -2160,6 +2317,33 @@ ngx_ssl_try_early_data(ngx_connection_t *c) c->recv_chain = ngx_ssl_recv_chain; c->send_chain = ngx_ssl_send_chain; + c->read->ready = 1; + c->write->ready = 1; + +#ifdef BIO_get_ktls_send + + if (BIO_get_ktls_send(SSL_get_wbio(c->ssl->connection)) == 1) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "BIO_get_ktls_send(): 1"); + c->ssl->sendfile = 1; + } + +#endif + + rc = ngx_ssl_ocsp_validate(c); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + c->read->handler = ngx_ssl_handshake_handler; + c->write->handler = ngx_ssl_handshake_handler; + return NGX_AGAIN; + } + + c->ssl->handshaked = 1; + return NGX_OK; } @@ -2235,6 +2419,10 @@ ngx_ssl_handshake_log(ngx_connection_t *c) #endif SSL_CIPHER *cipher; + if (!(c->log->log_level & NGX_LOG_DEBUG_EVENT)) { + return; + } + cipher = SSL_get_current_cipher(c->ssl->connection); if (cipher) { @@ -2785,10 +2973,11 @@ ngx_ssl_write_handler(ngx_event_t *wev) ngx_chain_t * ngx_ssl_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) { - int n; - ngx_uint_t flush; - ssize_t send, size; - ngx_buf_t *buf; + int n; + ngx_uint_t flush; + ssize_t send, size, file_size; + ngx_buf_t *buf; + ngx_chain_t *cl; if (!c->ssl->buffer) { @@ -2862,6 +3051,11 @@ ngx_ssl_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) continue; } + if (in->buf->in_file && c->ssl->sendfile) { + flush = 1; + break; + } + size = in->buf->last - in->buf->pos; if (size > buf->end - buf->last) { @@ -2893,8 +3087,35 @@ ngx_ssl_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) size = buf->last - buf->pos; if (size == 0) { + + if (in && in->buf->in_file && send < limit) { + + /* coalesce the neighbouring file bufs */ + + cl = in; + file_size = (size_t) ngx_chain_coalesce_file(&cl, limit - send); + + n = ngx_ssl_sendfile(c, in->buf, file_size); + + if (n == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + + if (n == NGX_AGAIN) { + break; + } + + in = ngx_chain_update_sent(in, n); + + send += n; + flush = 0; + + continue; + } + buf->flush = 0; c->buffered &= ~NGX_SSL_BUFFERED; + return in; } @@ -2919,7 +3140,7 @@ ngx_ssl_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) buf->pos = buf->start; buf->last = buf->start; - if (in == NULL || send == limit) { + if (in == NULL || send >= limit) { break; } } @@ -2985,6 +3206,18 @@ ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size) sslerr = SSL_get_error(c->ssl->connection, n); + if (sslerr == SSL_ERROR_ZERO_RETURN) { + + /* + * OpenSSL 1.1.1 fails to return SSL_ERROR_SYSCALL if an error + * happens during SSL_write() after close_notify alert from the + * peer, and returns SSL_ERROR_ZERO_RETURN instead, + * https://git.openssl.org/?p=openssl.git;a=commitdiff;h=8051ab2 + */ + + sslerr = SSL_ERROR_SYSCALL; + } + err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); @@ -3070,7 +3303,7 @@ ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size) #ifdef SSL_READ_EARLY_DATA_SUCCESS -ssize_t +static ssize_t ngx_ssl_write_early(ngx_connection_t *c, u_char *data, size_t size) { int n, sslerr; @@ -3185,6 +3418,183 @@ ngx_ssl_write_early(ngx_connection_t *c, u_char *data, size_t size) #endif +static ssize_t +ngx_ssl_sendfile(ngx_connection_t *c, ngx_buf_t *file, size_t size) +{ +#ifdef BIO_get_ktls_send + + int sslerr, flags; + ssize_t n; + ngx_err_t err; + + ngx_ssl_clear_error(c->log); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL to sendfile: @%O %uz", + file->file_pos, size); + + ngx_set_errno(0); + +#if (NGX_HAVE_SENDFILE_NODISKIO) + + flags = (c->busy_count <= 2) ? SF_NODISKIO : 0; + + if (file->file->directio) { + flags |= SF_NOCACHE; + } + +#else + flags = 0; +#endif + + n = SSL_sendfile(c->ssl->connection, file->file->fd, file->file_pos, + size, flags); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_sendfile: %d", n); + + if (n > 0) { + + if (c->ssl->saved_read_handler) { + + c->read->handler = c->ssl->saved_read_handler; + c->ssl->saved_read_handler = NULL; + c->read->ready = 1; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + ngx_post_event(c->read, &ngx_posted_events); + } + +#if (NGX_HAVE_SENDFILE_NODISKIO) + c->busy_count = 0; +#endif + + c->sent += n; + + return n; + } + + if (n == 0) { + + /* + * if sendfile returns zero, then someone has truncated the file, + * so the offset became beyond the end of the file + */ + + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "SSL_sendfile() reported that \"%s\" was truncated at %O", + file->file->name.data, file->file_pos); + + return NGX_ERROR; + } + + sslerr = SSL_get_error(c->ssl->connection, n); + + if (sslerr == SSL_ERROR_ZERO_RETURN) { + + /* + * OpenSSL fails to return SSL_ERROR_SYSCALL if an error + * happens during writing after close_notify alert from the + * peer, and returns SSL_ERROR_ZERO_RETURN instead + */ + + sslerr = SSL_ERROR_SYSCALL; + } + + if (sslerr == SSL_ERROR_SSL + && ERR_GET_REASON(ERR_peek_error()) == SSL_R_UNINITIALIZED + && ngx_errno != 0) + { + /* + * OpenSSL fails to return SSL_ERROR_SYSCALL if an error + * happens in sendfile(), and returns SSL_ERROR_SSL with + * SSL_R_UNINITIALIZED reason instead + */ + + sslerr = SSL_ERROR_SYSCALL; + } + + err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); + + if (sslerr == SSL_ERROR_WANT_WRITE) { + + if (c->ssl->saved_read_handler) { + + c->read->handler = c->ssl->saved_read_handler; + c->ssl->saved_read_handler = NULL; + c->read->ready = 1; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + ngx_post_event(c->read, &ngx_posted_events); + } + +#if (NGX_HAVE_SENDFILE_NODISKIO) + + if (ngx_errno == EBUSY) { + c->busy_count++; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_sendfile() busy, count:%d", c->busy_count); + + if (c->write->posted) { + ngx_delete_posted_event(c->write); + } + + ngx_post_event(c->write, &ngx_posted_next_events); + } + +#endif + + c->write->ready = 0; + return NGX_AGAIN; + } + + if (sslerr == SSL_ERROR_WANT_READ) { + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_sendfile: want read"); + + c->read->ready = 0; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + /* + * we do not set the timer because there is already + * the write event timer + */ + + if (c->ssl->saved_read_handler == NULL) { + c->ssl->saved_read_handler = c->read->handler; + c->read->handler = ngx_ssl_read_handler; + } + + return NGX_AGAIN; + } + + c->ssl->no_wait_shutdown = 1; + c->ssl->no_send_shutdown = 1; + c->write->error = 1; + + ngx_ssl_connection_error(c, sslerr, err, "SSL_sendfile() failed"); + +#else + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "SSL_sendfile() not available"); +#endif + + return NGX_ERROR; +} + + static void ngx_ssl_read_handler(ngx_event_t *rev) { @@ -3212,8 +3622,10 @@ ngx_ssl_free_buffer(ngx_connection_t *c) ngx_int_t ngx_ssl_shutdown(ngx_connection_t *c) { - int n, sslerr, mode; - ngx_err_t err; + int n, sslerr, mode; + ngx_int_t rc; + ngx_err_t err; + ngx_uint_t tries; #if (T_NGX_HAVE_DTLS) if (c->ssl->retrans && c->ssl->retrans->timer_set) { @@ -3221,6 +3633,10 @@ ngx_ssl_shutdown(ngx_connection_t *c) } #endif + rc = NGX_OK; + + ngx_ssl_ocsp_cleanup(c); + if (SSL_in_init(c->ssl->connection)) { /* * OpenSSL 1.0.2f complains if SSL_shutdown() is called during @@ -3252,13 +3668,10 @@ ngx_ssl_shutdown(ngx_connection_t *c) } #endif - SSL_free(c->ssl->connection); - c->ssl = NULL; - - return NGX_OK; + goto done; } - if (c->timedout) { + if (c->timedout || c->error || c->buffered) { mode = SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN; SSL_set_quiet_shutdown(c->ssl->connection, 1); @@ -3282,15 +3695,30 @@ ngx_ssl_shutdown(ngx_connection_t *c) ngx_ssl_clear_error(c->log); - n = SSL_shutdown(c->ssl->connection); + tries = 2; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_shutdown: %d", n); + for ( ;; ) { + + /* + * For bidirectional shutdown, SSL_shutdown() needs to be called + * twice: first call sends the "close notify" alert and returns 0, + * second call waits for the peer's "close notify" alert. + */ + + n = SSL_shutdown(c->ssl->connection); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_shutdown: %d", n); + + if (n == 1) { + goto done; + } - sslerr = 0; + if (n == 0 && tries-- > 1) { + continue; + } - /* before 0.9.8m SSL_shutdown() returned 0 instead of -1 on errors */ + /* before 0.9.8m SSL_shutdown() returned 0 instead of -1 on errors */ - if (n != 1 && ERR_peek_error()) { #if (NGX_SSL && NGX_SSL_ASYNC) if (c->async_enable && ngx_ssl_async_process_fds(c) == NGX_ERROR) { return NGX_ERROR; @@ -3300,60 +3728,54 @@ ngx_ssl_shutdown(ngx_connection_t *c) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); - } + if (sslerr == SSL_ERROR_WANT_READ || sslerr == SSL_ERROR_WANT_WRITE) { #if (NGX_SSL && NGX_SSL_ASYNC) - else if (c->async_enable && n == -1) { - sslerr = SSL_get_error(c->ssl->connection, n); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL_get_error async: %d", sslerr); - } -#endif - if (n == 1 || sslerr == 0 || sslerr == SSL_ERROR_ZERO_RETURN) { -#if (NGX_SSL && NGX_SSL_ASYNC) - if (c->async_enable) { - /* Ignore errors from ngx_ssl_async_process_fds as - we want to carry on and close the SSL connection - anyway. */ - ngx_ssl_async_process_fds(c); - if (ngx_del_async_conn) { - if (c->num_async_fds) { - ngx_del_async_conn(c, NGX_DISABLE_EVENT); - c->num_async_fds--; - } + if (c->async_enable && ngx_ssl_async_process_fds(c) == NGX_ERROR) { + return NGX_ERROR; } - ngx_del_conn(c, NGX_DISABLE_EVENT); - } #endif - SSL_free(c->ssl->connection); - c->ssl = NULL; + c->read->handler = ngx_ssl_shutdown_handler; + c->write->handler = ngx_ssl_shutdown_handler; - return NGX_OK; - } + if (sslerr == SSL_ERROR_WANT_READ) { + c->read->ready = 0; - if (sslerr == SSL_ERROR_WANT_READ || sslerr == SSL_ERROR_WANT_WRITE) { -#if (NGX_SSL && NGX_SSL_ASYNC) - if (c->async_enable && ngx_ssl_async_process_fds(c) == NGX_ERROR) { - return NGX_ERROR; - } -#endif - c->read->handler = ngx_ssl_shutdown_handler; - c->write->handler = ngx_ssl_shutdown_handler; + } else { + c->write->ready = 0; + } - if (ngx_handle_read_event(c->read, 0) != NGX_OK) { - return NGX_ERROR; - } + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + goto failed; + } - if (ngx_handle_write_event(c->write, 0) != NGX_OK) { - return NGX_ERROR; - } + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + goto failed; + } + + ngx_add_timer(c->read, 3000); - if (sslerr == SSL_ERROR_WANT_READ) { - ngx_add_timer(c->read, 30000); + return NGX_AGAIN; } - return NGX_AGAIN; - } + if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) { +#if (NGX_SSL && NGX_SSL_ASYNC) + if (c->async_enable) { + /* Ignore errors from ngx_ssl_async_process_fds as + we want to carry on and close the SSL connection + anyway. */ + ngx_ssl_async_process_fds(c); + if (ngx_del_async_conn) { + if (c->num_async_fds) { + ngx_del_async_conn(c, NGX_DISABLE_EVENT); + c->num_async_fds--; + } + } + ngx_del_conn(c, NGX_DISABLE_EVENT); + } +#endif + goto done; + } #if (NGX_SSL && NGX_SSL_ASYNC) if (c->async_enable) { @@ -3385,14 +3807,31 @@ ngx_ssl_shutdown(ngx_connection_t *c) ngx_del_conn(c, NGX_DISABLE_EVENT); } #endif - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; - ngx_ssl_connection_error(c, sslerr, err, "SSL_shutdown() failed"); + err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + + ngx_ssl_connection_error(c, sslerr, err, "SSL_shutdown() failed"); + + break; + } + +failed: + + rc = NGX_ERROR; + +done: + + if (c->ssl->shutdown_without_free) { + c->ssl->shutdown_without_free = 0; + c->recv = ngx_recv; + return rc; + } SSL_free(c->ssl->connection); c->ssl = NULL; + c->recv = ngx_recv; - return NGX_ERROR; + return rc; } @@ -3500,6 +3939,9 @@ ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, ngx_err_t err, #endif #ifdef SSL_R_CALLBACK_FAILED || n == SSL_R_CALLBACK_FAILED /* 234 */ +#endif +#ifdef SSL_R_NO_APPLICATION_PROTOCOL + || n == SSL_R_NO_APPLICATION_PROTOCOL /* 235 */ #endif || n == SSL_R_UNEXPECTED_MESSAGE /* 244 */ || n == SSL_R_UNEXPECTED_RECORD /* 245 */ @@ -3615,7 +4057,7 @@ ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, char *fmt, ...) for ( ;; ) { - n = ERR_peek_error_line_data(NULL, NULL, &data, &flags); + n = ERR_peek_error_data(&data, &flags); if (n == 0) { break; @@ -3781,8 +4223,9 @@ ngx_ssl_session_id_context(ngx_ssl_t *ssl, ngx_str_t *sess_ctx, } } - if (SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index) == NULL) { - + if (SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index) == NULL + && certificates != NULL) + { /* * If certificates are loaded dynamically, we use certificate * names as specified in the configuration (with variables). @@ -4476,9 +4919,6 @@ ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_ticket_key_t *key; const EVP_MD *digest; const EVP_CIPHER *cipher; -#if (NGX_DEBUG) - u_char buf[32]; -#endif c = ngx_ssl_get_connection(ssl_conn); ssl_ctx = c->ssl->session_ctx; @@ -4500,8 +4940,8 @@ ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, /* encrypt session ticket */ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ssl session ticket encrypt, key: \"%*s\" (%s session)", - ngx_hex_dump(buf, key[0].name, 16) - buf, buf, + "ssl session ticket encrypt, key: \"%*xs\" (%s session)", + (size_t) 16, key[0].name, SSL_session_reused(ssl_conn) ? "reused" : "new"); if (key[0].size == 48) { @@ -4547,17 +4987,16 @@ ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ssl session ticket decrypt, key: \"%*s\" not found", - ngx_hex_dump(buf, name, 16) - buf, buf); + "ssl session ticket decrypt, key: \"%*xs\" not found", + (size_t) 16, name); return 0; found: ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ssl session ticket decrypt, key: \"%*s\"%s", - ngx_hex_dump(buf, key[i].name, 16) - buf, buf, - (i == 0) ? " (default)" : ""); + "ssl session ticket decrypt, key: \"%*xs\"%s", + (size_t) 16, key[i].name, (i == 0) ? " (default)" : ""); if (key[i].size == 48) { cipher = EVP_aes_128_cbc(); @@ -4583,7 +5022,21 @@ ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, return -1; } - return (i == 0) ? 1 : 2 /* renew */; + /* renew if TLSv1.3 */ + +#ifdef TLS1_3_VERSION + if (SSL_version(ssl_conn) == TLS1_3_VERSION) { + return 2; + } +#endif + + /* renew if non-default key */ + + if (i != 0) { + return 2; + } + + return 1; } } @@ -4901,6 +5354,42 @@ ngx_ssl_get_ciphers(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) } +ngx_int_t +ngx_ssl_get_curve(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) +{ +#ifdef SSL_get_negotiated_group + + int nid; + + nid = SSL_get_negotiated_group(c->ssl->connection); + + if (nid != NID_undef) { + + if ((nid & TLSEXT_nid_unknown) == 0) { + s->len = ngx_strlen(OBJ_nid2sn(nid)); + s->data = (u_char *) OBJ_nid2sn(nid); + return NGX_OK; + } + + s->len = sizeof("0x0000") - 1; + + s->data = ngx_pnalloc(pool, s->len); + if (s->data == NULL) { + return NGX_ERROR; + } + + ngx_sprintf(s->data, "0x%04xd", nid & 0xffff); + + return NGX_OK; + } + +#endif + + s->len = 0; + return NGX_OK; +} + + ngx_int_t ngx_ssl_get_curves(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) { @@ -5068,6 +5557,36 @@ ngx_ssl_get_server_name(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) } +ngx_int_t +ngx_ssl_get_alpn_protocol(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) +{ +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + + unsigned int len; + const unsigned char *data; + + SSL_get0_alpn_selected(c->ssl->connection, &data, &len); + + if (len > 0) { + + s->data = ngx_pnalloc(pool, len); + if (s->data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(s->data, data, len); + s->len = len; + + return NGX_OK; + } + +#endif + + s->len = 0; + return NGX_OK; +} + + ngx_int_t ngx_ssl_get_raw_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) { @@ -5214,11 +5733,13 @@ ngx_ssl_get_subject_dn(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) bio = BIO_new(BIO_s_mem()); if (bio == NULL) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "BIO_new() failed"); X509_free(cert); return NGX_ERROR; } if (X509_NAME_print_ex(bio, name, 0, XN_FLAG_RFC2253) < 0) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "X509_NAME_print_ex() failed"); goto failed; } @@ -5266,11 +5787,13 @@ ngx_ssl_get_issuer_dn(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) bio = BIO_new(BIO_s_mem()); if (bio == NULL) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "BIO_new() failed"); X509_free(cert); return NGX_ERROR; } if (X509_NAME_print_ex(bio, name, 0, XN_FLAG_RFC2253) < 0) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "X509_NAME_print_ex() failed"); goto failed; } @@ -5319,6 +5842,11 @@ ngx_ssl_get_subject_dn_legacy(ngx_connection_t *c, ngx_pool_t *pool, } p = X509_NAME_oneline(name, NULL, 0); + if (p == NULL) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "X509_NAME_oneline() failed"); + X509_free(cert); + return NGX_ERROR; + } for (len = 0; p[len]; len++) { /* void */ } @@ -5362,6 +5890,11 @@ ngx_ssl_get_issuer_dn_legacy(ngx_connection_t *c, ngx_pool_t *pool, } p = X509_NAME_oneline(name, NULL, 0); + if (p == NULL) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "X509_NAME_oneline() failed"); + X509_free(cert); + return NGX_ERROR; + } for (len = 0; p[len]; len++) { /* void */ } @@ -5398,6 +5931,7 @@ ngx_ssl_get_serial_number(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) bio = BIO_new(BIO_s_mem()); if (bio == NULL) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "BIO_new() failed"); X509_free(cert); return NGX_ERROR; } @@ -5436,6 +5970,7 @@ ngx_ssl_get_fingerprint(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) } if (!X509_digest(cert, EVP_sha1(), buf, &len)) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "X509_digest() failed"); X509_free(cert); return NGX_ERROR; } @@ -5473,11 +6008,14 @@ ngx_ssl_get_client_verify(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) rc = SSL_get_verify_result(c->ssl->connection); if (rc == X509_V_OK) { - ngx_str_set(s, "SUCCESS"); - return NGX_OK; - } + if (ngx_ssl_ocsp_get_status(c, &str) == NGX_OK) { + ngx_str_set(s, "SUCCESS"); + return NGX_OK; + } - str = X509_verify_cert_error_string(rc); + } else { + str = X509_verify_cert_error_string(rc); + } s->data = ngx_pnalloc(pool, sizeof("FAILED:") - 1 + ngx_strlen(str)); if (s->data == NULL) { @@ -5506,6 +6044,7 @@ ngx_ssl_get_client_v_start(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) bio = BIO_new(BIO_s_mem()); if (bio == NULL) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "BIO_new() failed"); X509_free(cert); return NGX_ERROR; } @@ -5550,6 +6089,7 @@ ngx_ssl_get_client_v_end(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) bio = BIO_new(BIO_s_mem()); if (bio == NULL) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "BIO_new() failed"); X509_free(cert); return NGX_ERROR; } @@ -5592,9 +6132,9 @@ ngx_ssl_get_client_v_remain(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) } #if OPENSSL_VERSION_NUMBER > 0x10100000L - end = ngx_ssl_parse_time(X509_get0_notAfter(cert)); + end = ngx_ssl_parse_time(X509_get0_notAfter(cert), c->log); #else - end = ngx_ssl_parse_time(X509_get_notAfter(cert)); + end = ngx_ssl_parse_time(X509_get_notAfter(cert), c->log); #endif if (end == (time_t) NGX_ERROR) { @@ -5703,7 +6243,7 @@ ngx_ssl_parse_time( #if OPENSSL_VERSION_NUMBER > 0x10100000L const #endif - ASN1_TIME *asn1time) + ASN1_TIME *asn1time, ngx_log_t *log) { BIO *bio; char *value; @@ -5719,6 +6259,7 @@ ngx_ssl_parse_time( bio = BIO_new(BIO_s_mem()); if (bio == NULL) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, "BIO_new() failed"); return NGX_ERROR; } diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 0522f28e7c..4620d44629 100755 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -12,6 +12,8 @@ #include #include +#define OPENSSL_SUPPRESS_DEPRECATED + #include #include #include @@ -64,6 +66,19 @@ #endif +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined SSL_get_peer_certificate) +#define SSL_get_peer_certificate(s) SSL_get1_peer_certificate(s) +#endif + + +#if (OPENSSL_VERSION_NUMBER < 0x30000000L && !defined ERR_peek_error_data) +#define ERR_peek_error_data(d, f) ERR_peek_error_line_data(NULL, NULL, d, f) +#endif + + +typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; + + struct ngx_ssl_s { SSL_CTX *ctx; ngx_log_t *log; @@ -90,6 +105,8 @@ struct ngx_ssl_connection_s { ngx_event_handler_pt saved_read_handler; ngx_event_handler_pt saved_write_handler; + ngx_ssl_ocsp_t *ocsp; + u_char early_buf; #if (T_NGX_SSL_HANDSHAKE_TIME) @@ -98,18 +115,20 @@ struct ngx_ssl_connection_s { #endif unsigned handshaked:1; + unsigned handshake_rejected:1; unsigned renegotiation:1; unsigned buffer:1; + unsigned sendfile:1; unsigned no_wait_shutdown:1; unsigned no_send_shutdown:1; + unsigned shutdown_without_free:1; unsigned handshake_buffer_set:1; unsigned try_early_data:1; unsigned in_early:1; + unsigned in_ocsp:1; unsigned early_preread:1; unsigned write_blocked:1; - - #if (T_NGX_HAVE_DTLS) unsigned bio_changed:1; unsigned dtls_send:1; @@ -209,8 +228,14 @@ ngx_int_t ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify); ngx_int_t ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_resolver_t *resolver, ngx_msec_t resolver_timeout); -RSA *ngx_ssl_rsa512_key_callback(ngx_ssl_conn_t *ssl_conn, int is_export, - int key_length); +ngx_int_t ngx_ssl_ocsp(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder, + ngx_uint_t depth, ngx_shm_zone_t *shm_zone); +ngx_int_t ngx_ssl_ocsp_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl, + ngx_resolver_t *resolver, ngx_msec_t resolver_timeout); +ngx_int_t ngx_ssl_ocsp_validate(ngx_connection_t *c); +ngx_int_t ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s); +void ngx_ssl_ocsp_cleanup(ngx_connection_t *c); +ngx_int_t ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data); ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file); ngx_array_t *ngx_ssl_preserve_passwords(ngx_conf_t *cf, ngx_array_t *passwords); @@ -218,6 +243,9 @@ ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file); ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name); ngx_int_t ngx_ssl_early_data(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable); +ngx_int_t ngx_ssl_conf_commands(ngx_conf_t *cf, ngx_ssl_t *ssl, + ngx_array_t *commands); + ngx_int_t ngx_ssl_client_session_cache(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable); ngx_int_t ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx, @@ -226,6 +254,7 @@ ngx_int_t ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx, ngx_int_t ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths); ngx_int_t ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data); + ngx_int_t ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags); @@ -258,6 +287,8 @@ ngx_int_t ngx_ssl_get_cipher_name(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); ngx_int_t ngx_ssl_get_ciphers(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); +ngx_int_t ngx_ssl_get_curve(ngx_connection_t *c, ngx_pool_t *pool, + ngx_str_t *s); ngx_int_t ngx_ssl_get_curves(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); ngx_int_t ngx_ssl_get_session_id(ngx_connection_t *c, ngx_pool_t *pool, @@ -268,6 +299,8 @@ ngx_int_t ngx_ssl_get_early_data(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); ngx_int_t ngx_ssl_get_server_name(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); +ngx_int_t ngx_ssl_get_alpn_protocol(ngx_connection_t *c, ngx_pool_t *pool, + ngx_str_t *s); ngx_int_t ngx_ssl_get_raw_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); ngx_int_t ngx_ssl_get_certificate(ngx_connection_t *c, ngx_pool_t *pool, @@ -322,6 +355,7 @@ extern int ngx_ssl_connection_index; extern int ngx_ssl_server_conf_index; extern int ngx_ssl_session_cache_index; extern int ngx_ssl_session_ticket_keys_index; +extern int ngx_ssl_ocsp_index; extern int ngx_ssl_certificate_index; extern int ngx_ssl_next_certificate_index; extern int ngx_ssl_certificate_name_index; diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c index 5a7e40e42e..e3fa8c4e2b 100644 --- a/src/event/ngx_event_openssl_stapling.c +++ b/src/event/ngx_event_openssl_stapling.c @@ -22,6 +22,7 @@ typedef struct { ngx_msec_t resolver_timeout; ngx_addr_t *addrs; + ngx_uint_t naddrs; ngx_str_t host; ngx_str_t uri; in_port_t port; @@ -30,6 +31,7 @@ typedef struct { X509 *cert; X509 *issuer; + STACK_OF(X509) *chain; u_char *name; @@ -41,15 +43,66 @@ typedef struct { } ngx_ssl_stapling_t; +typedef struct { + ngx_addr_t *addrs; + ngx_uint_t naddrs; + + ngx_str_t host; + ngx_str_t uri; + in_port_t port; + ngx_uint_t depth; + + ngx_shm_zone_t *shm_zone; + + ngx_resolver_t *resolver; + ngx_msec_t resolver_timeout; +} ngx_ssl_ocsp_conf_t; + + +typedef struct { + ngx_rbtree_t rbtree; + ngx_rbtree_node_t sentinel; + ngx_queue_t expire_queue; +} ngx_ssl_ocsp_cache_t; + + +typedef struct { + ngx_str_node_t node; + ngx_queue_t queue; + int status; + time_t valid; +} ngx_ssl_ocsp_cache_node_t; + + typedef struct ngx_ssl_ocsp_ctx_s ngx_ssl_ocsp_ctx_t; + +struct ngx_ssl_ocsp_s { + STACK_OF(X509) *certs; + ngx_uint_t ncert; + + int cert_status; + ngx_int_t status; + + ngx_ssl_ocsp_conf_t *conf; + ngx_ssl_ocsp_ctx_t *ctx; +}; + + struct ngx_ssl_ocsp_ctx_s { + SSL_CTX *ssl_ctx; + X509 *cert; X509 *issuer; + STACK_OF(X509) *chain; + + int status; + time_t valid; u_char *name; ngx_uint_t naddrs; + ngx_uint_t naddr; ngx_addr_t *addrs; ngx_str_t host; @@ -64,17 +117,20 @@ struct ngx_ssl_ocsp_ctx_s { void (*handler)(ngx_ssl_ocsp_ctx_t *ctx); void *data; + ngx_str_t key; ngx_buf_t *request; ngx_buf_t *response; ngx_peer_connection_t peer; + ngx_shm_zone_t *shm_zone; + ngx_int_t (*process)(ngx_ssl_ocsp_ctx_t *ctx); ngx_uint_t state; ngx_uint_t code; ngx_uint_t count; - + ngx_uint_t flags; ngx_uint_t done; u_char *header_name_start; @@ -105,8 +161,14 @@ static time_t ngx_ssl_stapling_time(ASN1_GENERALIZEDTIME *asn1time); static void ngx_ssl_stapling_cleanup(void *data); -static ngx_ssl_ocsp_ctx_t *ngx_ssl_ocsp_start(void); +static void ngx_ssl_ocsp_validate_next(ngx_connection_t *c); +static void ngx_ssl_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx); +static ngx_int_t ngx_ssl_ocsp_responder(ngx_connection_t *c, + ngx_ssl_ocsp_ctx_t *ctx); + +static ngx_ssl_ocsp_ctx_t *ngx_ssl_ocsp_start(ngx_log_t *log); static void ngx_ssl_ocsp_done(ngx_ssl_ocsp_ctx_t *ctx); +static void ngx_ssl_ocsp_next(ngx_ssl_ocsp_ctx_t *ctx); static void ngx_ssl_ocsp_request(ngx_ssl_ocsp_ctx_t *ctx); static void ngx_ssl_ocsp_resolve_handler(ngx_resolver_ctx_t *resolve); static void ngx_ssl_ocsp_connect(ngx_ssl_ocsp_ctx_t *ctx); @@ -120,6 +182,11 @@ static ngx_int_t ngx_ssl_ocsp_parse_status_line(ngx_ssl_ocsp_ctx_t *ctx); static ngx_int_t ngx_ssl_ocsp_process_headers(ngx_ssl_ocsp_ctx_t *ctx); static ngx_int_t ngx_ssl_ocsp_parse_header_line(ngx_ssl_ocsp_ctx_t *ctx); static ngx_int_t ngx_ssl_ocsp_process_body(ngx_ssl_ocsp_ctx_t *ctx); +static ngx_int_t ngx_ssl_ocsp_verify(ngx_ssl_ocsp_ctx_t *ctx); + +static ngx_int_t ngx_ssl_ocsp_cache_lookup(ngx_ssl_ocsp_ctx_t *ctx); +static ngx_int_t ngx_ssl_ocsp_cache_store(ngx_ssl_ocsp_ctx_t *ctx); +static ngx_int_t ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx_t *ctx); static u_char *ngx_ssl_ocsp_log_error(ngx_log_t *log, u_char *buf, size_t len); @@ -173,6 +240,18 @@ ngx_ssl_stapling_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, X509 *cert, return NGX_ERROR; } +#ifdef SSL_CTRL_SELECT_CURRENT_CERT + /* OpenSSL 1.0.2+ */ + SSL_CTX_select_current_cert(ssl->ctx, cert); +#endif + +#ifdef SSL_CTRL_GET_EXTRA_CHAIN_CERTS + /* OpenSSL 1.0.1+ */ + SSL_CTX_get_extra_chain_certs(ssl->ctx, &staple->chain); +#else + staple->chain = ssl->ctx->extra_certs; +#endif + staple->ssl_ctx = ssl->ctx; staple->timeout = 60000; staple->verify = verify; @@ -289,29 +368,16 @@ ngx_ssl_stapling_issuer(ngx_conf_t *cf, ngx_ssl_t *ssl, X509 *cert, *issuer; X509_STORE *store; X509_STORE_CTX *store_ctx; - STACK_OF(X509) *chain; cert = staple->cert; -#ifdef SSL_CTRL_SELECT_CURRENT_CERT - /* OpenSSL 1.0.2+ */ - SSL_CTX_select_current_cert(ssl->ctx, cert); -#endif - -#ifdef SSL_CTRL_GET_EXTRA_CHAIN_CERTS - /* OpenSSL 1.0.1+ */ - SSL_CTX_get_extra_chain_certs(ssl->ctx, &chain); -#else - chain = ssl->ctx->extra_certs; -#endif - - n = sk_X509_num(chain); + n = sk_X509_num(staple->chain); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ssl->log, 0, "SSL get issuer: %d extra certs", n); for (i = 0; i < n; i++) { - issuer = sk_X509_value(chain, i); + issuer = sk_X509_value(staple->chain, i); if (X509_check_issued(issuer, cert) == X509_V_OK) { #if OPENSSL_VERSION_NUMBER >= 0x10100001L X509_up_ref(issuer); @@ -462,6 +528,7 @@ ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl, } staple->addrs = u.addrs; + staple->naddrs = u.naddrs; staple->host = u.host; staple->uri = u.uri; staple->port = u.port; @@ -559,16 +626,20 @@ ngx_ssl_stapling_update(ngx_ssl_stapling_t *staple) staple->loading = 1; - ctx = ngx_ssl_ocsp_start(); + ctx = ngx_ssl_ocsp_start(ngx_cycle->log); if (ctx == NULL) { return; } + ctx->ssl_ctx = staple->ssl_ctx; ctx->cert = staple->cert; ctx->issuer = staple->issuer; + ctx->chain = staple->chain; ctx->name = staple->name; + ctx->flags = (staple->verify ? OCSP_TRUSTOTHER : OCSP_NOVERIFY); ctx->addrs = staple->addrs; + ctx->naddrs = staple->naddrs; ctx->host = staple->host; ctx->uri = staple->uri; ctx->port = staple->port; @@ -589,137 +660,27 @@ ngx_ssl_stapling_update(ngx_ssl_stapling_t *staple) static void ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx) { - int n; - size_t len; - time_t now, valid; - ngx_str_t response; - X509_STORE *store; - const u_char *p; - STACK_OF(X509) *chain; - OCSP_CERTID *id; - OCSP_RESPONSE *ocsp; - OCSP_BASICRESP *basic; - ngx_ssl_stapling_t *staple; - ASN1_GENERALIZEDTIME *thisupdate, *nextupdate; + time_t now; + ngx_str_t response; + ngx_ssl_stapling_t *staple; staple = ctx->data; now = ngx_time(); - ocsp = NULL; - basic = NULL; - id = NULL; - - if (ctx->code != 200) { - goto error; - } - - /* check the response */ - - len = ctx->response->last - ctx->response->pos; - p = ctx->response->pos; - - ocsp = d2i_OCSP_RESPONSE(NULL, &p, len); - if (ocsp == NULL) { - ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0, - "d2i_OCSP_RESPONSE() failed"); - goto error; - } - - n = OCSP_response_status(ocsp); - - if (n != OCSP_RESPONSE_STATUS_SUCCESSFUL) { - ngx_log_error(NGX_LOG_ERR, ctx->log, 0, - "OCSP response not successful (%d: %s)", - n, OCSP_response_status_str(n)); - goto error; - } - - basic = OCSP_response_get1_basic(ocsp); - if (basic == NULL) { - ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0, - "OCSP_response_get1_basic() failed"); - goto error; - } - - store = SSL_CTX_get_cert_store(staple->ssl_ctx); - if (store == NULL) { - ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0, - "SSL_CTX_get_cert_store() failed"); - goto error; - } - -#ifdef SSL_CTRL_SELECT_CURRENT_CERT - /* OpenSSL 1.0.2+ */ - SSL_CTX_select_current_cert(staple->ssl_ctx, ctx->cert); -#endif - -#ifdef SSL_CTRL_GET_EXTRA_CHAIN_CERTS - /* OpenSSL 1.0.1+ */ - SSL_CTX_get_extra_chain_certs(staple->ssl_ctx, &chain); -#else - chain = staple->ssl_ctx->extra_certs; -#endif - - if (OCSP_basic_verify(basic, chain, store, - staple->verify ? OCSP_TRUSTOTHER : OCSP_NOVERIFY) - != 1) - { - ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0, - "OCSP_basic_verify() failed"); - goto error; - } - - id = OCSP_cert_to_id(NULL, ctx->cert, ctx->issuer); - if (id == NULL) { - ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0, - "OCSP_cert_to_id() failed"); - goto error; - } - if (OCSP_resp_find_status(basic, id, &n, NULL, NULL, - &thisupdate, &nextupdate) - != 1) - { - ngx_log_error(NGX_LOG_ERR, ctx->log, 0, - "certificate status not found in the OCSP response"); + if (ngx_ssl_ocsp_verify(ctx) != NGX_OK) { goto error; } - if (n != V_OCSP_CERTSTATUS_GOOD) { + if (ctx->status != V_OCSP_CERTSTATUS_GOOD) { ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "certificate status \"%s\" in the OCSP response", - OCSP_cert_status_str(n)); - goto error; - } - - if (OCSP_check_validity(thisupdate, nextupdate, 300, -1) != 1) { - ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0, - "OCSP_check_validity() failed"); + OCSP_cert_status_str(ctx->status)); goto error; } - if (nextupdate) { - valid = ngx_ssl_stapling_time(nextupdate); - if (valid == (time_t) NGX_ERROR) { - ngx_log_error(NGX_LOG_ERR, ctx->log, 0, - "invalid nextUpdate time in certificate status"); - goto error; - } - - } else { - valid = NGX_MAX_TIME_T_VALUE; - } - - OCSP_CERTID_free(id); - OCSP_BASICRESP_free(basic); - OCSP_RESPONSE_free(ocsp); - - id = NULL; - basic = NULL; - ocsp = NULL; - /* copy the response to memory not in ctx->pool */ - response.len = len; + response.len = ctx->response->last - ctx->response->pos; response.data = ngx_alloc(response.len, ctx->log); if (response.data == NULL) { @@ -728,16 +689,12 @@ ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx) ngx_memcpy(response.data, ctx->response->pos, response.len); - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0, - "ssl ocsp response, %s, %uz", - OCSP_cert_status_str(n), response.len); - if (staple->staple.data) { ngx_free(staple->staple.data); } staple->staple = response; - staple->valid = valid; + staple->valid = ctx->valid; /* * refresh before the response expires, @@ -745,7 +702,7 @@ ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx) */ staple->loading = 0; - staple->refresh = ngx_max(ngx_min(valid - 300, now + 3600), now + 300); + staple->refresh = ngx_max(ngx_min(ctx->valid - 300, now + 3600), now + 300); ngx_ssl_ocsp_done(ctx); return; @@ -755,18 +712,6 @@ ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx) staple->loading = 0; staple->refresh = now + 300; - if (id) { - OCSP_CERTID_free(id); - } - - if (basic) { - OCSP_BASICRESP_free(basic); - } - - if (ocsp) { - OCSP_RESPONSE_free(ocsp); - } - ngx_ssl_ocsp_done(ctx); } @@ -820,366 +765,905 @@ ngx_ssl_stapling_cleanup(void *data) } -static ngx_ssl_ocsp_ctx_t * -ngx_ssl_ocsp_start(void) +ngx_int_t +ngx_ssl_ocsp(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder, + ngx_uint_t depth, ngx_shm_zone_t *shm_zone) { - ngx_log_t *log; - ngx_pool_t *pool; - ngx_ssl_ocsp_ctx_t *ctx; - - pool = ngx_create_pool(2048, ngx_cycle->log); - if (pool == NULL) { - return NULL; - } + ngx_url_t u; + ngx_ssl_ocsp_conf_t *ocf; - ctx = ngx_pcalloc(pool, sizeof(ngx_ssl_ocsp_ctx_t)); - if (ctx == NULL) { - ngx_destroy_pool(pool); - return NULL; + ocf = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_ocsp_conf_t)); + if (ocf == NULL) { + return NGX_ERROR; } - log = ngx_palloc(pool, sizeof(ngx_log_t)); - if (log == NULL) { - ngx_destroy_pool(pool); - return NULL; - } + ocf->depth = depth; + ocf->shm_zone = shm_zone; - ctx->pool = pool; + if (responder->len) { + ngx_memzero(&u, sizeof(ngx_url_t)); - *log = *ctx->pool->log; + u.url = *responder; + u.default_port = 80; + u.uri_part = 1; - ctx->pool->log = log; - ctx->log = log; + if (u.url.len > 7 + && ngx_strncasecmp(u.url.data, (u_char *) "http://", 7) == 0) + { + u.url.len -= 7; + u.url.data += 7; - log->handler = ngx_ssl_ocsp_log_error; - log->data = ctx; - log->action = "requesting certificate status"; + } else { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "invalid URL prefix in OCSP responder \"%V\" " + "in \"ssl_ocsp_responder\"", &u.url); + return NGX_ERROR; + } - return ctx; -} + if (ngx_parse_url(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "%s in OCSP responder \"%V\" " + "in \"ssl_ocsp_responder\"", u.err, &u.url); + } + return NGX_ERROR; + } -static void -ngx_ssl_ocsp_done(ngx_ssl_ocsp_ctx_t *ctx) -{ - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, - "ssl ocsp done"); + ocf->addrs = u.addrs; + ocf->naddrs = u.naddrs; + ocf->host = u.host; + ocf->uri = u.uri; + ocf->port = u.port; + } - if (ctx->peer.connection) { - ngx_close_connection(ctx->peer.connection); + if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_ocsp_index, ocf) == 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set_ex_data() failed"); + return NGX_ERROR; } - ngx_destroy_pool(ctx->pool); + return NGX_OK; } -static void -ngx_ssl_ocsp_error(ngx_ssl_ocsp_ctx_t *ctx) +ngx_int_t +ngx_ssl_ocsp_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl, + ngx_resolver_t *resolver, ngx_msec_t resolver_timeout) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, - "ssl ocsp error"); + ngx_ssl_ocsp_conf_t *ocf; - ctx->code = 0; - ctx->handler(ctx); + ocf = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_ocsp_index); + ocf->resolver = resolver; + ocf->resolver_timeout = resolver_timeout; + + return NGX_OK; } -static void -ngx_ssl_ocsp_request(ngx_ssl_ocsp_ctx_t *ctx) +ngx_int_t +ngx_ssl_ocsp_validate(ngx_connection_t *c) { - ngx_resolver_ctx_t *resolve, temp; + X509 *cert; + SSL_CTX *ssl_ctx; + ngx_int_t rc; + X509_STORE *store; + X509_STORE_CTX *store_ctx; + STACK_OF(X509) *chain; + ngx_ssl_ocsp_t *ocsp; + ngx_ssl_ocsp_conf_t *ocf; + + if (c->ssl->in_ocsp) { + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, - "ssl ocsp request"); + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + return NGX_ERROR; + } - if (ngx_ssl_ocsp_create_request(ctx) != NGX_OK) { - ngx_ssl_ocsp_error(ctx); - return; + return NGX_AGAIN; } - if (ctx->resolver) { - /* resolve OCSP responder hostname */ + ssl_ctx = SSL_get_SSL_CTX(c->ssl->connection); - temp.name = ctx->host; + ocf = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_ocsp_index); + if (ocf == NULL) { + return NGX_OK; + } - resolve = ngx_resolve_start(ctx->resolver, &temp); - if (resolve == NULL) { - ngx_ssl_ocsp_error(ctx); - return; - } + if (SSL_get_verify_result(c->ssl->connection) != X509_V_OK) { + return NGX_OK; + } - if (resolve == NGX_NO_RESOLVER) { - ngx_log_error(NGX_LOG_WARN, ctx->log, 0, - "no resolver defined to resolve %V", &ctx->host); - goto connect; - } + cert = SSL_get_peer_certificate(c->ssl->connection); + if (cert == NULL) { + return NGX_OK; + } - resolve->name = ctx->host; - resolve->handler = ngx_ssl_ocsp_resolve_handler; - resolve->data = ctx; - resolve->timeout = ctx->resolver_timeout; + ocsp = ngx_pcalloc(c->pool, sizeof(ngx_ssl_ocsp_t)); + if (ocsp == NULL) { + X509_free(cert); + return NGX_ERROR; + } - if (ngx_resolve_name(resolve) != NGX_OK) { - ngx_ssl_ocsp_error(ctx); - return; - } + c->ssl->ocsp = ocsp; - return; + ocsp->status = NGX_AGAIN; + ocsp->cert_status = V_OCSP_CERTSTATUS_GOOD; + ocsp->conf = ocf; + +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) + + ocsp->certs = SSL_get0_verified_chain(c->ssl->connection); + + if (ocsp->certs) { + ocsp->certs = X509_chain_up_ref(ocsp->certs); + if (ocsp->certs == NULL) { + X509_free(cert); + return NGX_ERROR; + } } -connect: +#endif - ngx_ssl_ocsp_connect(ctx); -} + if (ocsp->certs == NULL) { + store = SSL_CTX_get_cert_store(ssl_ctx); + if (store == NULL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "SSL_CTX_get_cert_store() failed"); + X509_free(cert); + return NGX_ERROR; + } + store_ctx = X509_STORE_CTX_new(); + if (store_ctx == NULL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "X509_STORE_CTX_new() failed"); + X509_free(cert); + return NGX_ERROR; + } -static void -ngx_ssl_ocsp_resolve_handler(ngx_resolver_ctx_t *resolve) -{ - ngx_ssl_ocsp_ctx_t *ctx = resolve->data; + chain = SSL_get_peer_cert_chain(c->ssl->connection); - u_char *p; - size_t len; - socklen_t socklen; - ngx_uint_t i; - struct sockaddr *sockaddr; + if (X509_STORE_CTX_init(store_ctx, store, cert, chain) == 0) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "X509_STORE_CTX_init() failed"); + X509_STORE_CTX_free(store_ctx); + X509_free(cert); + return NGX_ERROR; + } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, - "ssl ocsp resolve handler"); + rc = X509_verify_cert(store_ctx); + if (rc <= 0) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "X509_verify_cert() failed"); + X509_STORE_CTX_free(store_ctx); + X509_free(cert); + return NGX_ERROR; + } - if (resolve->state) { - ngx_log_error(NGX_LOG_ERR, ctx->log, 0, - "%V could not be resolved (%i: %s)", - &resolve->name, resolve->state, - ngx_resolver_strerror(resolve->state)); - goto failed; - } + ocsp->certs = X509_STORE_CTX_get1_chain(store_ctx); + if (ocsp->certs == NULL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "X509_STORE_CTX_get1_chain() failed"); + X509_STORE_CTX_free(store_ctx); + X509_free(cert); + return NGX_ERROR; + } -#if (NGX_DEBUG) - { - u_char text[NGX_SOCKADDR_STRLEN]; - ngx_str_t addr; + X509_STORE_CTX_free(store_ctx); + } - addr.data = text; + X509_free(cert); - for (i = 0; i < resolve->naddrs; i++) { - addr.len = ngx_sock_ntop(resolve->addrs[i].sockaddr, - resolve->addrs[i].socklen, - text, NGX_SOCKADDR_STRLEN, 0); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ssl ocsp validate, certs:%d", sk_X509_num(ocsp->certs)); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0, - "name was resolved to %V", &addr); + ngx_ssl_ocsp_validate_next(c); + if (ocsp->status == NGX_AGAIN) { + c->ssl->in_ocsp = 1; + return NGX_AGAIN; } - } -#endif - ctx->naddrs = resolve->naddrs; - ctx->addrs = ngx_pcalloc(ctx->pool, ctx->naddrs * sizeof(ngx_addr_t)); + return NGX_OK; +} - if (ctx->addrs == NULL) { - goto failed; - } - for (i = 0; i < resolve->naddrs; i++) { +static void +ngx_ssl_ocsp_validate_next(ngx_connection_t *c) +{ + ngx_int_t rc; + ngx_uint_t n; + ngx_ssl_ocsp_t *ocsp; + ngx_ssl_ocsp_ctx_t *ctx; + ngx_ssl_ocsp_conf_t *ocf; - socklen = resolve->addrs[i].socklen; + ocsp = c->ssl->ocsp; + ocf = ocsp->conf; - sockaddr = ngx_palloc(ctx->pool, socklen); - if (sockaddr == NULL) { - goto failed; - } + n = sk_X509_num(ocsp->certs); - ngx_memcpy(sockaddr, resolve->addrs[i].sockaddr, socklen); - ngx_inet_set_port(sockaddr, ctx->port); + for ( ;; ) { - ctx->addrs[i].sockaddr = sockaddr; - ctx->addrs[i].socklen = socklen; + if (ocsp->ncert == n - 1 || (ocf->depth == 2 && ocsp->ncert == 1)) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ssl ocsp validated, certs:%ui", ocsp->ncert); + rc = NGX_OK; + goto done; + } - p = ngx_pnalloc(ctx->pool, NGX_SOCKADDR_STRLEN); - if (p == NULL) { - goto failed; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ssl ocsp validate cert:%ui", ocsp->ncert); + + ctx = ngx_ssl_ocsp_start(c->log); + if (ctx == NULL) { + rc = NGX_ERROR; + goto done; } - len = ngx_sock_ntop(sockaddr, socklen, p, NGX_SOCKADDR_STRLEN, 1); + ocsp->ctx = ctx; - ctx->addrs[i].name.len = len; - ctx->addrs[i].name.data = p; - } + ctx->ssl_ctx = SSL_get_SSL_CTX(c->ssl->connection); + ctx->cert = sk_X509_value(ocsp->certs, ocsp->ncert); + ctx->issuer = sk_X509_value(ocsp->certs, ocsp->ncert + 1); + ctx->chain = ocsp->certs; - ngx_resolve_name_done(resolve); + ctx->resolver = ocf->resolver; + ctx->resolver_timeout = ocf->resolver_timeout; - ngx_ssl_ocsp_connect(ctx); - return; + ctx->handler = ngx_ssl_ocsp_handler; + ctx->data = c; -failed: + ctx->shm_zone = ocf->shm_zone; - ngx_resolve_name_done(resolve); - ngx_ssl_ocsp_error(ctx); -} + ctx->addrs = ocf->addrs; + ctx->naddrs = ocf->naddrs; + ctx->host = ocf->host; + ctx->uri = ocf->uri; + ctx->port = ocf->port; + rc = ngx_ssl_ocsp_responder(c, ctx); + if (rc != NGX_OK) { + goto done; + } -static void -ngx_ssl_ocsp_connect(ngx_ssl_ocsp_ctx_t *ctx) -{ - ngx_int_t rc; + if (ctx->uri.len == 0) { + ngx_str_set(&ctx->uri, "/"); + } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, - "ssl ocsp connect"); + ocsp->ncert++; - /* TODO: use all ip addresses */ + rc = ngx_ssl_ocsp_cache_lookup(ctx); - ctx->peer.sockaddr = ctx->addrs[0].sockaddr; - ctx->peer.socklen = ctx->addrs[0].socklen; - ctx->peer.name = &ctx->addrs[0].name; - ctx->peer.get = ngx_event_get_peer; - ctx->peer.log = ctx->log; - ctx->peer.log_error = NGX_ERROR_ERR; + if (rc == NGX_ERROR) { + goto done; + } - rc = ngx_event_connect_peer(&ctx->peer); + if (rc == NGX_DECLINED) { + break; + } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, - "ssl ocsp connect peer done"); + /* rc == NGX_OK */ - if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) { - ngx_ssl_ocsp_error(ctx); - return; - } + if (ctx->status != V_OCSP_CERTSTATUS_GOOD) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp cached status \"%s\"", + OCSP_cert_status_str(ctx->status)); + ocsp->cert_status = ctx->status; + goto done; + } - ctx->peer.connection->data = ctx; - ctx->peer.connection->pool = ctx->pool; + ocsp->ctx = NULL; + ngx_ssl_ocsp_done(ctx); + } - ctx->peer.connection->read->handler = ngx_ssl_ocsp_read_handler; - ctx->peer.connection->write->handler = ngx_ssl_ocsp_write_handler; + ngx_ssl_ocsp_request(ctx); + return; - ctx->process = ngx_ssl_ocsp_process_status_line; +done: - ngx_add_timer(ctx->peer.connection->read, ctx->timeout); - ngx_add_timer(ctx->peer.connection->write, ctx->timeout); + ocsp->status = rc; - if (rc == NGX_OK) { - ngx_ssl_ocsp_write_handler(ctx->peer.connection->write); - return; + if (c->ssl->in_ocsp) { + c->ssl->handshaked = 1; + c->ssl->handler(c); } } static void -ngx_ssl_ocsp_write_handler(ngx_event_t *wev) +ngx_ssl_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx) { - ssize_t n, size; - ngx_connection_t *c; - ngx_ssl_ocsp_ctx_t *ctx; - - c = wev->data; - ctx = c->data; + ngx_int_t rc; + ngx_ssl_ocsp_t *ocsp; + ngx_connection_t *c; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, wev->log, 0, - "ssl ocsp write handler"); + c = ctx->data; + ocsp = c->ssl->ocsp; + ocsp->ctx = NULL; - if (wev->timedout) { - ngx_log_error(NGX_LOG_ERR, wev->log, NGX_ETIMEDOUT, - "OCSP responder timed out"); - ngx_ssl_ocsp_error(ctx); - return; + rc = ngx_ssl_ocsp_verify(ctx); + if (rc != NGX_OK) { + goto done; } - size = ctx->request->last - ctx->request->pos; - - n = ngx_send(c, ctx->request->pos, size); + rc = ngx_ssl_ocsp_cache_store(ctx); + if (rc != NGX_OK) { + goto done; + } - if (n == NGX_ERROR) { - ngx_ssl_ocsp_error(ctx); - return; + if (ctx->status != V_OCSP_CERTSTATUS_GOOD) { + ocsp->cert_status = ctx->status; + goto done; } - if (n > 0) { - ctx->request->pos += n; + ngx_ssl_ocsp_done(ctx); - if (n == size) { - wev->handler = ngx_ssl_ocsp_dummy_handler; + ngx_ssl_ocsp_validate_next(c); - if (wev->timer_set) { - ngx_del_timer(wev); - } + return; - if (ngx_handle_write_event(wev, 0) != NGX_OK) { - ngx_ssl_ocsp_error(ctx); - } +done: - return; - } - } + ocsp->status = rc; + ngx_ssl_ocsp_done(ctx); - if (!wev->timer_set) { - ngx_add_timer(wev, ctx->timeout); + if (c->ssl->in_ocsp) { + c->ssl->handshaked = 1; + c->ssl->handler(c); } } -static void -ngx_ssl_ocsp_read_handler(ngx_event_t *rev) +static ngx_int_t +ngx_ssl_ocsp_responder(ngx_connection_t *c, ngx_ssl_ocsp_ctx_t *ctx) { - ssize_t n, size; - ngx_int_t rc; - ngx_connection_t *c; - ngx_ssl_ocsp_ctx_t *ctx; + char *s; + ngx_str_t responder; + ngx_url_t u; + STACK_OF(OPENSSL_STRING) *aia; - c = rev->data; - ctx = c->data; + if (ctx->host.len) { + return NGX_OK; + } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, - "ssl ocsp read handler"); + /* extract OCSP responder URL from certificate */ - if (rev->timedout) { - ngx_log_error(NGX_LOG_ERR, rev->log, NGX_ETIMEDOUT, - "OCSP responder timed out"); - ngx_ssl_ocsp_error(ctx); - return; + aia = X509_get1_ocsp(ctx->cert); + if (aia == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no OCSP responder URL in certificate"); + return NGX_ERROR; } - if (ctx->response == NULL) { - ctx->response = ngx_create_temp_buf(ctx->pool, 16384); - if (ctx->response == NULL) { - ngx_ssl_ocsp_error(ctx); - return; - } +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + s = sk_OPENSSL_STRING_value(aia, 0); +#else + s = sk_value(aia, 0); +#endif + if (s == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no OCSP responder URL in certificate"); + X509_email_free(aia); + return NGX_ERROR; } - for ( ;; ) { + responder.len = ngx_strlen(s); + responder.data = ngx_palloc(ctx->pool, responder.len); + if (responder.data == NULL) { + X509_email_free(aia); + return NGX_ERROR; + } - size = ctx->response->end - ctx->response->last; + ngx_memcpy(responder.data, s, responder.len); + X509_email_free(aia); - n = ngx_recv(c, ctx->response->last, size); + ngx_memzero(&u, sizeof(ngx_url_t)); - if (n > 0) { - ctx->response->last += n; + u.url = responder; + u.default_port = 80; + u.uri_part = 1; + u.no_resolve = 1; - rc = ctx->process(ctx); + if (u.url.len > 7 + && ngx_strncasecmp(u.url.data, (u_char *) "http://", 7) == 0) + { + u.url.len -= 7; + u.url.data += 7; - if (rc == NGX_ERROR) { - ngx_ssl_ocsp_error(ctx); - return; - } + } else { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "invalid URL prefix in OCSP responder \"%V\" " + "in certificate", &u.url); + return NGX_ERROR; + } - continue; + if (ngx_parse_url(ctx->pool, &u) != NGX_OK) { + if (u.err) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "%s in OCSP responder \"%V\" in certificate", + u.err, &u.url); } - if (n == NGX_AGAIN) { + return NGX_ERROR; + } - if (ngx_handle_read_event(rev, 0) != NGX_OK) { - ngx_ssl_ocsp_error(ctx); - } + if (u.host.len == 0) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "empty host in OCSP responder in certificate"); + return NGX_ERROR; + } - return; - } + ctx->addrs = u.addrs; + ctx->naddrs = u.naddrs; + ctx->host = u.host; + ctx->uri = u.uri; + ctx->port = u.port; - break; - } + return NGX_OK; +} - ctx->done = 1; - rc = ctx->process(ctx); +ngx_int_t +ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s) +{ + ngx_ssl_ocsp_t *ocsp; + + ocsp = c->ssl->ocsp; + if (ocsp == NULL) { + return NGX_OK; + } + + if (ocsp->status == NGX_ERROR) { + *s = "certificate status request failed"; + return NGX_DECLINED; + } + + switch (ocsp->cert_status) { + + case V_OCSP_CERTSTATUS_GOOD: + return NGX_OK; + + case V_OCSP_CERTSTATUS_REVOKED: + *s = "certificate revoked"; + break; + + default: /* V_OCSP_CERTSTATUS_UNKNOWN */ + *s = "certificate status unknown"; + } + + return NGX_DECLINED; +} + + +void +ngx_ssl_ocsp_cleanup(ngx_connection_t *c) +{ + ngx_ssl_ocsp_t *ocsp; + + ocsp = c->ssl->ocsp; + if (ocsp == NULL) { + return; + } + + if (ocsp->ctx) { + ngx_ssl_ocsp_done(ocsp->ctx); + ocsp->ctx = NULL; + } + + if (ocsp->certs) { + sk_X509_pop_free(ocsp->certs, X509_free); + ocsp->certs = NULL; + } +} + + +static ngx_ssl_ocsp_ctx_t * +ngx_ssl_ocsp_start(ngx_log_t *log) +{ + ngx_pool_t *pool; + ngx_ssl_ocsp_ctx_t *ctx; + + pool = ngx_create_pool(2048, log); + if (pool == NULL) { + return NULL; + } + + ctx = ngx_pcalloc(pool, sizeof(ngx_ssl_ocsp_ctx_t)); + if (ctx == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + + log = ngx_palloc(pool, sizeof(ngx_log_t)); + if (log == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + + ctx->pool = pool; + + *log = *ctx->pool->log; + + ctx->pool->log = log; + ctx->log = log; + + log->handler = ngx_ssl_ocsp_log_error; + log->data = ctx; + log->action = "requesting certificate status"; + + return ctx; +} + + +static void +ngx_ssl_ocsp_done(ngx_ssl_ocsp_ctx_t *ctx) +{ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp done"); + + if (ctx->peer.connection) { + ngx_close_connection(ctx->peer.connection); + } + + ngx_destroy_pool(ctx->pool); +} + + +static void +ngx_ssl_ocsp_error(ngx_ssl_ocsp_ctx_t *ctx) +{ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp error"); + + ctx->code = 0; + ctx->handler(ctx); +} + + +static void +ngx_ssl_ocsp_next(ngx_ssl_ocsp_ctx_t *ctx) +{ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp next"); + + if (++ctx->naddr >= ctx->naddrs) { + ngx_ssl_ocsp_error(ctx); + return; + } + + ctx->request->pos = ctx->request->start; + + if (ctx->response) { + ctx->response->last = ctx->response->pos; + } + + if (ctx->peer.connection) { + ngx_close_connection(ctx->peer.connection); + ctx->peer.connection = NULL; + } + + ctx->state = 0; + ctx->count = 0; + ctx->done = 0; + + ngx_ssl_ocsp_connect(ctx); +} + + +static void +ngx_ssl_ocsp_request(ngx_ssl_ocsp_ctx_t *ctx) +{ + ngx_resolver_ctx_t *resolve, temp; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp request"); + + if (ngx_ssl_ocsp_create_request(ctx) != NGX_OK) { + ngx_ssl_ocsp_error(ctx); + return; + } + + if (ctx->resolver) { + /* resolve OCSP responder hostname */ + + temp.name = ctx->host; + + resolve = ngx_resolve_start(ctx->resolver, &temp); + if (resolve == NULL) { + ngx_ssl_ocsp_error(ctx); + return; + } + + if (resolve == NGX_NO_RESOLVER) { + if (ctx->naddrs == 0) { + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "no resolver defined to resolve %V", &ctx->host); + + ngx_ssl_ocsp_error(ctx); + return; + } + + ngx_log_error(NGX_LOG_WARN, ctx->log, 0, + "no resolver defined to resolve %V", &ctx->host); + goto connect; + } + + resolve->name = ctx->host; + resolve->handler = ngx_ssl_ocsp_resolve_handler; + resolve->data = ctx; + resolve->timeout = ctx->resolver_timeout; + + if (ngx_resolve_name(resolve) != NGX_OK) { + ngx_ssl_ocsp_error(ctx); + return; + } + + return; + } + +connect: + + ngx_ssl_ocsp_connect(ctx); +} + + +static void +ngx_ssl_ocsp_resolve_handler(ngx_resolver_ctx_t *resolve) +{ + ngx_ssl_ocsp_ctx_t *ctx = resolve->data; + + u_char *p; + size_t len; + socklen_t socklen; + ngx_uint_t i; + struct sockaddr *sockaddr; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp resolve handler"); + + if (resolve->state) { + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "%V could not be resolved (%i: %s)", + &resolve->name, resolve->state, + ngx_resolver_strerror(resolve->state)); + goto failed; + } + +#if (NGX_DEBUG) + { + u_char text[NGX_SOCKADDR_STRLEN]; + ngx_str_t addr; + + addr.data = text; + + for (i = 0; i < resolve->naddrs; i++) { + addr.len = ngx_sock_ntop(resolve->addrs[i].sockaddr, + resolve->addrs[i].socklen, + text, NGX_SOCKADDR_STRLEN, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "name was resolved to %V", &addr); + + } + } +#endif + + ctx->naddrs = resolve->naddrs; + ctx->addrs = ngx_pcalloc(ctx->pool, ctx->naddrs * sizeof(ngx_addr_t)); + + if (ctx->addrs == NULL) { + goto failed; + } + + for (i = 0; i < resolve->naddrs; i++) { + + socklen = resolve->addrs[i].socklen; + + sockaddr = ngx_palloc(ctx->pool, socklen); + if (sockaddr == NULL) { + goto failed; + } + + ngx_memcpy(sockaddr, resolve->addrs[i].sockaddr, socklen); + ngx_inet_set_port(sockaddr, ctx->port); + + ctx->addrs[i].sockaddr = sockaddr; + ctx->addrs[i].socklen = socklen; + + p = ngx_pnalloc(ctx->pool, NGX_SOCKADDR_STRLEN); + if (p == NULL) { + goto failed; + } + + len = ngx_sock_ntop(sockaddr, socklen, p, NGX_SOCKADDR_STRLEN, 1); + + ctx->addrs[i].name.len = len; + ctx->addrs[i].name.data = p; + } + + ngx_resolve_name_done(resolve); + + ngx_ssl_ocsp_connect(ctx); + return; + +failed: + + ngx_resolve_name_done(resolve); + ngx_ssl_ocsp_error(ctx); +} + + +static void +ngx_ssl_ocsp_connect(ngx_ssl_ocsp_ctx_t *ctx) +{ + ngx_int_t rc; + ngx_addr_t *addr; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp connect %ui/%ui", ctx->naddr, ctx->naddrs); + + addr = &ctx->addrs[ctx->naddr]; + + ctx->peer.sockaddr = addr->sockaddr; + ctx->peer.socklen = addr->socklen; + ctx->peer.name = &addr->name; + ctx->peer.get = ngx_event_get_peer; + ctx->peer.log = ctx->log; + ctx->peer.log_error = NGX_ERROR_ERR; + + rc = ngx_event_connect_peer(&ctx->peer); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp connect peer done"); + + if (rc == NGX_ERROR) { + ngx_ssl_ocsp_error(ctx); + return; + } + + if (rc == NGX_BUSY || rc == NGX_DECLINED) { + ngx_ssl_ocsp_next(ctx); + return; + } + + ctx->peer.connection->data = ctx; + ctx->peer.connection->pool = ctx->pool; + + ctx->peer.connection->read->handler = ngx_ssl_ocsp_read_handler; + ctx->peer.connection->write->handler = ngx_ssl_ocsp_write_handler; + + ctx->process = ngx_ssl_ocsp_process_status_line; + + if (ctx->timeout) { + ngx_add_timer(ctx->peer.connection->read, ctx->timeout); + ngx_add_timer(ctx->peer.connection->write, ctx->timeout); + } + + if (rc == NGX_OK) { + ngx_ssl_ocsp_write_handler(ctx->peer.connection->write); + return; + } +} + + +static void +ngx_ssl_ocsp_write_handler(ngx_event_t *wev) +{ + ssize_t n, size; + ngx_connection_t *c; + ngx_ssl_ocsp_ctx_t *ctx; + + c = wev->data; + ctx = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, wev->log, 0, + "ssl ocsp write handler"); + + if (wev->timedout) { + ngx_log_error(NGX_LOG_ERR, wev->log, NGX_ETIMEDOUT, + "OCSP responder timed out"); + ngx_ssl_ocsp_next(ctx); + return; + } + + size = ctx->request->last - ctx->request->pos; + + n = ngx_send(c, ctx->request->pos, size); + + if (n == NGX_ERROR) { + ngx_ssl_ocsp_next(ctx); + return; + } + + if (n > 0) { + ctx->request->pos += n; + + if (n == size) { + wev->handler = ngx_ssl_ocsp_dummy_handler; + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_ssl_ocsp_error(ctx); + } + + return; + } + } + + if (!wev->timer_set && ctx->timeout) { + ngx_add_timer(wev, ctx->timeout); + } +} + + +static void +ngx_ssl_ocsp_read_handler(ngx_event_t *rev) +{ + ssize_t n, size; + ngx_int_t rc; + ngx_connection_t *c; + ngx_ssl_ocsp_ctx_t *ctx; + + c = rev->data; + ctx = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, + "ssl ocsp read handler"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_ERR, rev->log, NGX_ETIMEDOUT, + "OCSP responder timed out"); + ngx_ssl_ocsp_next(ctx); + return; + } + + if (ctx->response == NULL) { + ctx->response = ngx_create_temp_buf(ctx->pool, 16384); + if (ctx->response == NULL) { + ngx_ssl_ocsp_error(ctx); + return; + } + } + + for ( ;; ) { + + size = ctx->response->end - ctx->response->last; + + n = ngx_recv(c, ctx->response->last, size); + + if (n > 0) { + ctx->response->last += n; + + rc = ctx->process(ctx); + + if (rc == NGX_ERROR) { + ngx_ssl_ocsp_next(ctx); + return; + } + + continue; + } + + if (n == NGX_AGAIN) { + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_ssl_ocsp_error(ctx); + } + + return; + } + + break; + } + + ctx->done = 1; + + rc = ctx->process(ctx); if (rc == NGX_DONE) { /* ctx->handler() was called */ @@ -1189,7 +1673,7 @@ ngx_ssl_ocsp_read_handler(ngx_event_t *rev) ngx_log_error(NGX_LOG_ERR, ctx->log, 0, "OCSP responder prematurely closed connection"); - ngx_ssl_ocsp_error(ctx); + ngx_ssl_ocsp_next(ctx); } @@ -1484,43 +1968,273 @@ ngx_ssl_ocsp_parse_status_line(ngx_ssl_ocsp_ctx_t *ctx) break; } - if (ch < '0' || ch > '9') { - return NGX_ERROR; + if (ch < '0' || ch > '9') { + return NGX_ERROR; + } + + ctx->code = ctx->code * 10 + (ch - '0'); + + if (++ctx->count == 3) { + state = sw_space_after_status; + ctx->header_start = p - 2; + } + + break; + + /* space or end of line */ + case sw_space_after_status: + switch (ch) { + case ' ': + state = sw_status_text; + break; + case '.': /* IIS may send 403.1, 403.2, etc */ + state = sw_status_text; + break; + case CR: + state = sw_almost_done; + break; + case LF: + ctx->header_end = p; + goto done; + default: + return NGX_ERROR; + } + break; + + /* any text until end of line */ + case sw_status_text: + switch (ch) { + case CR: + state = sw_almost_done; + break; + case LF: + ctx->header_end = p; + goto done; + } + break; + + /* end of status line */ + case sw_almost_done: + switch (ch) { + case LF: + ctx->header_end = p - 1; + goto done; + default: + return NGX_ERROR; + } + } + } + + b->pos = p; + ctx->state = state; + + return NGX_AGAIN; + +done: + + b->pos = p + 1; + ctx->state = sw_start; + + return NGX_OK; +} + + +static ngx_int_t +ngx_ssl_ocsp_process_headers(ngx_ssl_ocsp_ctx_t *ctx) +{ + size_t len; + ngx_int_t rc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp process headers"); + + for ( ;; ) { + rc = ngx_ssl_ocsp_parse_header_line(ctx); + + if (rc == NGX_OK) { + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp header \"%*s: %*s\"", + ctx->header_name_end - ctx->header_name_start, + ctx->header_name_start, + ctx->header_end - ctx->header_start, + ctx->header_start); + + len = ctx->header_name_end - ctx->header_name_start; + + if (len == sizeof("Content-Type") - 1 + && ngx_strncasecmp(ctx->header_name_start, + (u_char *) "Content-Type", + sizeof("Content-Type") - 1) + == 0) + { + len = ctx->header_end - ctx->header_start; + + if (len != sizeof("application/ocsp-response") - 1 + || ngx_strncasecmp(ctx->header_start, + (u_char *) "application/ocsp-response", + sizeof("application/ocsp-response") - 1) + != 0) + { + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "OCSP responder sent invalid " + "\"Content-Type\" header: \"%*s\"", + ctx->header_end - ctx->header_start, + ctx->header_start); + return NGX_ERROR; + } + + continue; + } + + /* TODO: honor Content-Length */ + + continue; + } + + if (rc == NGX_DONE) { + break; + } + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + /* rc == NGX_ERROR */ + + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "OCSP responder sent invalid response"); + + return NGX_ERROR; + } + + ctx->process = ngx_ssl_ocsp_process_body; + return ctx->process(ctx); +} + + +static ngx_int_t +ngx_ssl_ocsp_parse_header_line(ngx_ssl_ocsp_ctx_t *ctx) +{ + u_char c, ch, *p; + enum { + sw_start = 0, + sw_name, + sw_space_before_value, + sw_value, + sw_space_after_value, + sw_almost_done, + sw_header_almost_done + } state; + + state = ctx->state; + + for (p = ctx->response->pos; p < ctx->response->last; p++) { + ch = *p; + +#if 0 + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "s:%d in:'%02Xd:%c'", state, ch, ch); +#endif + + switch (state) { + + /* first char */ + case sw_start: + + switch (ch) { + case CR: + ctx->header_end = p; + state = sw_header_almost_done; + break; + case LF: + ctx->header_end = p; + goto header_done; + default: + state = sw_name; + ctx->header_name_start = p; + + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + break; + } + + if (ch >= '0' && ch <= '9') { + break; + } + + return NGX_ERROR; + } + break; + + /* header name */ + case sw_name: + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + break; + } + + if (ch == ':') { + ctx->header_name_end = p; + state = sw_space_before_value; + break; + } + + if (ch == '-') { + break; + } + + if (ch >= '0' && ch <= '9') { + break; } - ctx->code = ctx->code * 10 + (ch - '0'); + if (ch == CR) { + ctx->header_name_end = p; + ctx->header_start = p; + ctx->header_end = p; + state = sw_almost_done; + break; + } - if (++ctx->count == 3) { - state = sw_space_after_status; - ctx->header_start = p - 2; + if (ch == LF) { + ctx->header_name_end = p; + ctx->header_start = p; + ctx->header_end = p; + goto done; } - break; + return NGX_ERROR; - /* space or end of line */ - case sw_space_after_status: + /* space* before header value */ + case sw_space_before_value: switch (ch) { case ' ': - state = sw_status_text; - break; - case '.': /* IIS may send 403.1, 403.2, etc */ - state = sw_status_text; break; case CR: + ctx->header_start = p; + ctx->header_end = p; state = sw_almost_done; break; case LF: + ctx->header_start = p; ctx->header_end = p; goto done; default: - return NGX_ERROR; + ctx->header_start = p; + state = sw_value; + break; } break; - /* any text until end of line */ - case sw_status_text: + /* header value */ + case sw_value: switch (ch) { + case ' ': + ctx->header_end = p; + state = sw_space_after_value; + break; case CR: + ctx->header_end = p; state = sw_almost_done; break; case LF: @@ -1529,305 +2243,429 @@ ngx_ssl_ocsp_parse_status_line(ngx_ssl_ocsp_ctx_t *ctx) } break; - /* end of status line */ + /* space* before end of header line */ + case sw_space_after_value: + switch (ch) { + case ' ': + break; + case CR: + state = sw_almost_done; + break; + case LF: + goto done; + default: + state = sw_value; + break; + } + break; + + /* end of header line */ case sw_almost_done: switch (ch) { case LF: - ctx->header_end = p - 1; goto done; default: return NGX_ERROR; } + + /* end of header */ + case sw_header_almost_done: + switch (ch) { + case LF: + goto header_done; + default: + return NGX_ERROR; + } } } - b->pos = p; + ctx->response->pos = p; ctx->state = state; return NGX_AGAIN; done: - b->pos = p + 1; + ctx->response->pos = p + 1; ctx->state = sw_start; return NGX_OK; + +header_done: + + ctx->response->pos = p + 1; + ctx->state = sw_start; + + return NGX_DONE; } static ngx_int_t -ngx_ssl_ocsp_process_headers(ngx_ssl_ocsp_ctx_t *ctx) +ngx_ssl_ocsp_process_body(ngx_ssl_ocsp_ctx_t *ctx) { - size_t len; - ngx_int_t rc; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, - "ssl ocsp process headers"); + "ssl ocsp process body"); - for ( ;; ) { - rc = ngx_ssl_ocsp_parse_header_line(ctx); + if (ctx->done) { + ctx->handler(ctx); + return NGX_DONE; + } - if (rc == NGX_OK) { + return NGX_AGAIN; +} - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, ctx->log, 0, - "ssl ocsp header \"%*s: %*s\"", - ctx->header_name_end - ctx->header_name_start, - ctx->header_name_start, - ctx->header_end - ctx->header_start, - ctx->header_start); - len = ctx->header_name_end - ctx->header_name_start; +static ngx_int_t +ngx_ssl_ocsp_verify(ngx_ssl_ocsp_ctx_t *ctx) +{ + int n; + size_t len; + X509_STORE *store; + const u_char *p; + OCSP_CERTID *id; + OCSP_RESPONSE *ocsp; + OCSP_BASICRESP *basic; + ASN1_GENERALIZEDTIME *thisupdate, *nextupdate; - if (len == sizeof("Content-Type") - 1 - && ngx_strncasecmp(ctx->header_name_start, - (u_char *) "Content-Type", - sizeof("Content-Type") - 1) - == 0) - { - len = ctx->header_end - ctx->header_start; + ocsp = NULL; + basic = NULL; + id = NULL; - if (len != sizeof("application/ocsp-response") - 1 - || ngx_strncasecmp(ctx->header_start, - (u_char *) "application/ocsp-response", - sizeof("application/ocsp-response") - 1) - != 0) - { - ngx_log_error(NGX_LOG_ERR, ctx->log, 0, - "OCSP responder sent invalid " - "\"Content-Type\" header: \"%*s\"", - ctx->header_end - ctx->header_start, - ctx->header_start); - return NGX_ERROR; - } + if (ctx->code != 200) { + goto error; + } - continue; - } + /* check the response */ - /* TODO: honor Content-Length */ + len = ctx->response->last - ctx->response->pos; + p = ctx->response->pos; - continue; + ocsp = d2i_OCSP_RESPONSE(NULL, &p, len); + if (ocsp == NULL) { + ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0, + "d2i_OCSP_RESPONSE() failed"); + goto error; + } + + n = OCSP_response_status(ocsp); + + if (n != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "OCSP response not successful (%d: %s)", + n, OCSP_response_status_str(n)); + goto error; + } + + basic = OCSP_response_get1_basic(ocsp); + if (basic == NULL) { + ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0, + "OCSP_response_get1_basic() failed"); + goto error; + } + + store = SSL_CTX_get_cert_store(ctx->ssl_ctx); + if (store == NULL) { + ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0, + "SSL_CTX_get_cert_store() failed"); + goto error; + } + + if (OCSP_basic_verify(basic, ctx->chain, store, ctx->flags) != 1) { + ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0, + "OCSP_basic_verify() failed"); + goto error; + } + + id = OCSP_cert_to_id(NULL, ctx->cert, ctx->issuer); + if (id == NULL) { + ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0, + "OCSP_cert_to_id() failed"); + goto error; + } + + if (OCSP_resp_find_status(basic, id, &ctx->status, NULL, NULL, + &thisupdate, &nextupdate) + != 1) + { + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "certificate status not found in the OCSP response"); + goto error; + } + + if (OCSP_check_validity(thisupdate, nextupdate, 300, -1) != 1) { + ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0, + "OCSP_check_validity() failed"); + goto error; + } + + if (nextupdate) { + ctx->valid = ngx_ssl_stapling_time(nextupdate); + if (ctx->valid == (time_t) NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "invalid nextUpdate time in certificate status"); + goto error; } - if (rc == NGX_DONE) { - break; - } + } else { + ctx->valid = NGX_MAX_TIME_T_VALUE; + } + + OCSP_CERTID_free(id); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(ocsp); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp response, %s, %uz", + OCSP_cert_status_str(ctx->status), len); + + return NGX_OK; + +error: + + if (id) { + OCSP_CERTID_free(id); + } + + if (basic) { + OCSP_BASICRESP_free(basic); + } + + if (ocsp) { + OCSP_RESPONSE_free(ocsp); + } + + return NGX_ERROR; +} + + +ngx_int_t +ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data) +{ + size_t len; + ngx_slab_pool_t *shpool; + ngx_ssl_ocsp_cache_t *cache; + + if (data) { + shm_zone->data = data; + return NGX_OK; + } + + shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; + + if (shm_zone->shm.exists) { + shm_zone->data = shpool->data; + return NGX_OK; + } + + cache = ngx_slab_alloc(shpool, sizeof(ngx_ssl_ocsp_cache_t)); + if (cache == NULL) { + return NGX_ERROR; + } + + shpool->data = cache; + shm_zone->data = cache; - if (rc == NGX_AGAIN) { - return NGX_AGAIN; - } + ngx_rbtree_init(&cache->rbtree, &cache->sentinel, + ngx_str_rbtree_insert_value); - /* rc == NGX_ERROR */ + ngx_queue_init(&cache->expire_queue); - ngx_log_error(NGX_LOG_ERR, ctx->log, 0, - "OCSP responder sent invalid response"); + len = sizeof(" in OCSP cache \"\"") + shm_zone->shm.name.len; + shpool->log_ctx = ngx_slab_alloc(shpool, len); + if (shpool->log_ctx == NULL) { return NGX_ERROR; } - ctx->process = ngx_ssl_ocsp_process_body; - return ctx->process(ctx); + ngx_sprintf(shpool->log_ctx, " in OCSP cache \"%V\"%Z", + &shm_zone->shm.name); + + shpool->log_nomem = 0; + + return NGX_OK; } static ngx_int_t -ngx_ssl_ocsp_parse_header_line(ngx_ssl_ocsp_ctx_t *ctx) +ngx_ssl_ocsp_cache_lookup(ngx_ssl_ocsp_ctx_t *ctx) { - u_char c, ch, *p; - enum { - sw_start = 0, - sw_name, - sw_space_before_value, - sw_value, - sw_space_after_value, - sw_almost_done, - sw_header_almost_done - } state; + uint32_t hash; + ngx_shm_zone_t *shm_zone; + ngx_slab_pool_t *shpool; + ngx_ssl_ocsp_cache_t *cache; + ngx_ssl_ocsp_cache_node_t *node; - state = ctx->state; + shm_zone = ctx->shm_zone; - for (p = ctx->response->pos; p < ctx->response->last; p++) { - ch = *p; + if (shm_zone == NULL) { + return NGX_DECLINED; + } -#if 0 - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ctx->log, 0, - "s:%d in:'%02Xd:%c'", state, ch, ch); -#endif + if (ngx_ssl_ocsp_create_key(ctx) != NGX_OK) { + return NGX_ERROR; + } - switch (state) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, "ssl ocsp cache lookup"); - /* first char */ - case sw_start: + cache = shm_zone->data; + shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; + hash = ngx_hash_key(ctx->key.data, ctx->key.len); - switch (ch) { - case CR: - ctx->header_end = p; - state = sw_header_almost_done; - break; - case LF: - ctx->header_end = p; - goto header_done; - default: - state = sw_name; - ctx->header_name_start = p; + ngx_shmtx_lock(&shpool->mutex); - c = (u_char) (ch | 0x20); - if (c >= 'a' && c <= 'z') { - break; - } + node = (ngx_ssl_ocsp_cache_node_t *) + ngx_str_rbtree_lookup(&cache->rbtree, &ctx->key, hash); - if (ch >= '0' && ch <= '9') { - break; - } + if (node) { + if (node->valid > ngx_time()) { + ctx->status = node->status; + ngx_shmtx_unlock(&shpool->mutex); - return NGX_ERROR; - } - break; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp cache hit, %s", + OCSP_cert_status_str(ctx->status)); - /* header name */ - case sw_name: - c = (u_char) (ch | 0x20); - if (c >= 'a' && c <= 'z') { - break; - } + return NGX_OK; + } - if (ch == ':') { - ctx->header_name_end = p; - state = sw_space_before_value; - break; - } + ngx_queue_remove(&node->queue); + ngx_rbtree_delete(&cache->rbtree, &node->node.node); + ngx_slab_free_locked(shpool, node); - if (ch == '-') { - break; - } + ngx_shmtx_unlock(&shpool->mutex); - if (ch >= '0' && ch <= '9') { - break; - } + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp cache expired"); - if (ch == CR) { - ctx->header_name_end = p; - ctx->header_start = p; - ctx->header_end = p; - state = sw_almost_done; - break; - } + return NGX_DECLINED; + } - if (ch == LF) { - ctx->header_name_end = p; - ctx->header_start = p; - ctx->header_end = p; - goto done; - } + ngx_shmtx_unlock(&shpool->mutex); - return NGX_ERROR; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, "ssl ocsp cache miss"); - /* space* before header value */ - case sw_space_before_value: - switch (ch) { - case ' ': - break; - case CR: - ctx->header_start = p; - ctx->header_end = p; - state = sw_almost_done; - break; - case LF: - ctx->header_start = p; - ctx->header_end = p; - goto done; - default: - ctx->header_start = p; - state = sw_value; - break; - } - break; + return NGX_DECLINED; +} - /* header value */ - case sw_value: - switch (ch) { - case ' ': - ctx->header_end = p; - state = sw_space_after_value; - break; - case CR: - ctx->header_end = p; - state = sw_almost_done; - break; - case LF: - ctx->header_end = p; - goto done; - } - break; - /* space* before end of header line */ - case sw_space_after_value: - switch (ch) { - case ' ': - break; - case CR: - state = sw_almost_done; - break; - case LF: - goto done; - default: - state = sw_value; - break; - } - break; +static ngx_int_t +ngx_ssl_ocsp_cache_store(ngx_ssl_ocsp_ctx_t *ctx) +{ + time_t now, valid; + uint32_t hash; + ngx_queue_t *q; + ngx_shm_zone_t *shm_zone; + ngx_slab_pool_t *shpool; + ngx_ssl_ocsp_cache_t *cache; + ngx_ssl_ocsp_cache_node_t *node; - /* end of header line */ - case sw_almost_done: - switch (ch) { - case LF: - goto done; - default: - return NGX_ERROR; - } + shm_zone = ctx->shm_zone; - /* end of header */ - case sw_header_almost_done: - switch (ch) { - case LF: - goto header_done; - default: - return NGX_ERROR; - } - } + if (shm_zone == NULL) { + return NGX_OK; } - ctx->response->pos = p; - ctx->state = state; + valid = ctx->valid; - return NGX_AGAIN; + now = ngx_time(); -done: + if (valid < now) { + return NGX_OK; + } - ctx->response->pos = p + 1; - ctx->state = sw_start; + if (valid == NGX_MAX_TIME_T_VALUE) { + valid = now + 3600; + } - return NGX_OK; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp cache store, valid:%T", valid - now); -header_done: + cache = shm_zone->data; + shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; + hash = ngx_hash_key(ctx->key.data, ctx->key.len); - ctx->response->pos = p + 1; - ctx->state = sw_start; + ngx_shmtx_lock(&shpool->mutex); - return NGX_DONE; + node = ngx_slab_calloc_locked(shpool, + sizeof(ngx_ssl_ocsp_cache_node_t) + ctx->key.len); + if (node == NULL) { + + if (!ngx_queue_empty(&cache->expire_queue)) { + q = ngx_queue_last(&cache->expire_queue); + node = ngx_queue_data(q, ngx_ssl_ocsp_cache_node_t, queue); + + ngx_rbtree_delete(&cache->rbtree, &node->node.node); + ngx_queue_remove(q); + ngx_slab_free_locked(shpool, node); + + node = ngx_slab_alloc_locked(shpool, + sizeof(ngx_ssl_ocsp_cache_node_t) + ctx->key.len); + } + + if (node == NULL) { + ngx_shmtx_unlock(&shpool->mutex); + ngx_log_error(NGX_LOG_ALERT, ctx->log, 0, + "could not allocate new entry%s", shpool->log_ctx); + return NGX_ERROR; + } + } + + node->node.str.len = ctx->key.len; + node->node.str.data = (u_char *) node + sizeof(ngx_ssl_ocsp_cache_node_t); + ngx_memcpy(node->node.str.data, ctx->key.data, ctx->key.len); + node->node.node.key = hash; + node->status = ctx->status; + node->valid = valid; + + ngx_rbtree_insert(&cache->rbtree, &node->node.node); + ngx_queue_insert_head(&cache->expire_queue, &node->queue); + + ngx_shmtx_unlock(&shpool->mutex); + + return NGX_OK; } static ngx_int_t -ngx_ssl_ocsp_process_body(ngx_ssl_ocsp_ctx_t *ctx) +ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx_t *ctx) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, - "ssl ocsp process body"); + u_char *p; + X509_NAME *name; + ASN1_INTEGER *serial; - if (ctx->done) { - ctx->handler(ctx); - return NGX_DONE; + p = ngx_pnalloc(ctx->pool, 60); + if (p == NULL) { + return NGX_ERROR; } - return NGX_AGAIN; + ctx->key.data = p; + ctx->key.len = 60; + + name = X509_get_subject_name(ctx->issuer); + if (X509_NAME_digest(name, EVP_sha1(), p, NULL) == 0) { + return NGX_ERROR; + } + + p += 20; + + if (X509_pubkey_digest(ctx->issuer, EVP_sha1(), p, NULL) == 0) { + return NGX_ERROR; + } + + p += 20; + + serial = X509_get_serialNumber(ctx->cert); + if (serial->length > 20) { + return NGX_ERROR; + } + + p = ngx_cpymem(p, serial->data, serial->length); + ngx_memzero(p, 20 - serial->length); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0, + "ssl ocsp key %xV", &ctx->key); + + return NGX_OK; } @@ -1891,4 +2729,50 @@ ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl, } +ngx_int_t +ngx_ssl_ocsp(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder, + ngx_uint_t depth, ngx_shm_zone_t *shm_zone) +{ + ngx_log_error(NGX_LOG_EMERG, ssl->log, 0, + "\"ssl_ocsp\" is not supported on this platform"); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_ssl_ocsp_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl, + ngx_resolver_t *resolver, ngx_msec_t resolver_timeout) +{ + return NGX_OK; +} + + +ngx_int_t +ngx_ssl_ocsp_validate(ngx_connection_t *c) +{ + return NGX_OK; +} + + +ngx_int_t +ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s) +{ + return NGX_OK; +} + + +void +ngx_ssl_ocsp_cleanup(ngx_connection_t *c) +{ +} + + +ngx_int_t +ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data) +{ + return NGX_OK; +} + + #endif diff --git a/src/event/ngx_event_pipe.c b/src/event/ngx_event_pipe.c index 531b13aade..54412e130f 100644 --- a/src/event/ngx_event_pipe.c +++ b/src/event/ngx_event_pipe.c @@ -960,6 +960,22 @@ ngx_event_pipe_copy_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) return NGX_OK; } + if (p->upstream_done) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0, + "input data after close"); + return NGX_OK; + } + + if (p->length == 0) { + p->upstream_done = 1; + + ngx_log_error(NGX_LOG_WARN, p->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); + + return NGX_OK; + } + cl = ngx_chain_get_free_buf(p->pool, &p->free); if (cl == NULL) { return NGX_ERROR; @@ -987,6 +1003,18 @@ ngx_event_pipe_copy_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) return NGX_OK; } + if (b->last - b->pos > p->length) { + + ngx_log_error(NGX_LOG_WARN, p->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); + + b->last = b->pos + p->length; + p->upstream_done = 1; + + return NGX_OK; + } + p->length -= b->last - b->pos; return NGX_OK; diff --git a/src/event/ngx_event_timer.c b/src/event/ngx_event_timer.c index 698b88faec..35052bc294 100644 --- a/src/event/ngx_event_timer.c +++ b/src/event/ngx_event_timer.c @@ -73,7 +73,7 @@ ngx_event_expire_timers(void) return; } - ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer)); + ev = ngx_rbtree_data(node, ngx_event_t, timer); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0, "event timer del: %d: %M", @@ -113,7 +113,7 @@ ngx_event_no_timers_left(void) node; node = ngx_rbtree_next(&ngx_event_timer_rbtree, node)) { - ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer)); + ev = ngx_rbtree_data(node, ngx_event_t, timer); if (!ev->cancelable) { return NGX_AGAIN; diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c index 5572830503..a524ae04d7 100644 --- a/src/event/ngx_event_udp.c +++ b/src/event/ngx_event_udp.c @@ -363,6 +363,8 @@ ngx_event_recvmsg(ngx_event_t *ev) c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + c->start_time = ngx_current_msec; + #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_handled, 1); #endif diff --git a/src/http/modules/ngx_http_auth_basic_module.c b/src/http/modules/ngx_http_auth_basic_module.c index ed9df34300..069331982c 100644 --- a/src/http/modules/ngx_http_auth_basic_module.c +++ b/src/http/modules/ngx_http_auth_basic_module.c @@ -16,7 +16,7 @@ typedef struct { ngx_http_complex_value_t *realm; - ngx_http_complex_value_t user_file; + ngx_http_complex_value_t *user_file; } ngx_http_auth_basic_loc_conf_t; @@ -107,7 +107,7 @@ ngx_http_auth_basic_handler(ngx_http_request_t *r) alcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_basic_module); - if (alcf->realm == NULL || alcf->user_file.value.data == NULL) { + if (alcf->realm == NULL || alcf->user_file == NULL) { return NGX_DECLINED; } @@ -133,7 +133,7 @@ ngx_http_auth_basic_handler(ngx_http_request_t *r) return NGX_HTTP_INTERNAL_SERVER_ERROR; } - if (ngx_http_complex_value(r, &alcf->user_file, &user_file) != NGX_OK) { + if (ngx_http_complex_value(r, alcf->user_file, &user_file) != NGX_OK) { return NGX_ERROR; } @@ -357,6 +357,9 @@ ngx_http_auth_basic_create_loc_conf(ngx_conf_t *cf) return NULL; } + conf->realm = NGX_CONF_UNSET_PTR; + conf->user_file = NGX_CONF_UNSET_PTR; + return conf; } @@ -367,13 +370,8 @@ ngx_http_auth_basic_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ngx_http_auth_basic_loc_conf_t *prev = parent; ngx_http_auth_basic_loc_conf_t *conf = child; - if (conf->realm == NULL) { - conf->realm = prev->realm; - } - - if (conf->user_file.value.data == NULL) { - conf->user_file = prev->user_file; - } + ngx_conf_merge_ptr_value(conf->realm, prev->realm, NULL); + ngx_conf_merge_ptr_value(conf->user_file, prev->user_file, NULL); return NGX_CONF_OK; } @@ -406,17 +404,22 @@ ngx_http_auth_basic_user_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_str_t *value; ngx_http_compile_complex_value_t ccv; - if (alcf->user_file.value.data) { + if (alcf->user_file != NGX_CONF_UNSET_PTR) { return "is duplicate"; } + alcf->user_file = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); + if (alcf->user_file == NULL) { + return NGX_CONF_ERROR; + } + value = cf->args->elts; ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); ccv.cf = cf; ccv.value = &value[1]; - ccv.complex_value = &alcf->user_file; + ccv.complex_value = alcf->user_file; ccv.zero = 1; ccv.conf_prefix = 1; diff --git a/src/http/modules/ngx_http_dav_module.c b/src/http/modules/ngx_http_dav_module.c index 8b69e6f384..0cc9ae18bf 100644 --- a/src/http/modules/ngx_http_dav_module.c +++ b/src/http/modules/ngx_http_dav_module.c @@ -1072,6 +1072,10 @@ ngx_http_dav_error(ngx_log_t *log, ngx_err_t err, ngx_int_t not_found, static ngx_int_t ngx_http_dav_location(ngx_http_request_t *r) { + u_char *p; + size_t len; + uintptr_t escape; + r->headers_out.location = ngx_list_push(&r->headers_out.headers); if (r->headers_out.location == NULL) { return NGX_ERROR; @@ -1079,7 +1083,26 @@ ngx_http_dav_location(ngx_http_request_t *r) r->headers_out.location->hash = 1; ngx_str_set(&r->headers_out.location->key, "Location"); - r->headers_out.location->value = r->uri; + + escape = 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, NGX_ESCAPE_URI); + + if (escape) { + len = r->uri.len + escape; + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + ngx_http_clear_location(r); + return NGX_ERROR; + } + + r->headers_out.location->value.len = len; + r->headers_out.location->value.data = p; + + ngx_escape_uri(p, r->uri.data, r->uri.len, NGX_ESCAPE_URI); + + } else { + r->headers_out.location->value = r->uri; + } return NGX_OK; } diff --git a/src/http/modules/ngx_http_fastcgi_module.c b/src/http/modules/ngx_http_fastcgi_module.c index 98cdf74d44..e715fc7c54 100644 --- a/src/http/modules/ngx_http_fastcgi_module.c +++ b/src/http/modules/ngx_http_fastcgi_module.c @@ -81,12 +81,15 @@ typedef struct { size_t length; size_t padding; + off_t rest; + ngx_chain_t *free; ngx_chain_t *busy; unsigned fastcgi_stdout:1; unsigned large_stderr:1; unsigned header_sent:1; + unsigned closed:1; ngx_array_t *split_parts; @@ -2025,10 +2028,12 @@ ngx_http_fastcgi_process_header(ngx_http_request_t *r) break; } - /* there was error while a header line parsing */ + /* rc == NGX_HTTP_PARSE_INVALID_HEADER */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "upstream sent invalid header"); + "upstream sent invalid header: \"%*s\\x%02xd...\"", + r->header_end - r->header_name_start, + r->header_name_start, *r->header_end); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } @@ -2084,13 +2089,31 @@ ngx_http_fastcgi_process_header(ngx_http_request_t *r) static ngx_int_t ngx_http_fastcgi_input_filter_init(void *data) { - ngx_http_request_t *r = data; + ngx_http_request_t *r = data; + + ngx_http_upstream_t *u; + ngx_http_fastcgi_ctx_t *f; ngx_http_fastcgi_loc_conf_t *flcf; + u = r->upstream; + + f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module); flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module); - r->upstream->pipe->length = flcf->keep_conn ? - (off_t) sizeof(ngx_http_fastcgi_header_t) : -1; + u->pipe->length = flcf->keep_conn ? + (off_t) sizeof(ngx_http_fastcgi_header_t) : -1; + + if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT + || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED) + { + f->rest = 0; + + } else if (r->method == NGX_HTTP_HEAD) { + f->rest = -2; + + } else { + f->rest = u->headers_in.content_length_n; + } return NGX_OK; } @@ -2115,6 +2138,15 @@ ngx_http_fastcgi_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) f = ngx_http_get_module_ctx(r, ngx_http_fastcgi_module); flcf = ngx_http_get_module_loc_conf(r, ngx_http_fastcgi_module); + if (p->upstream_done || f->closed) { + r->upstream->keepalive = 0; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0, + "http fastcgi data after close"); + + return NGX_OK; + } + b = NULL; prev = &buf->shadow; @@ -2137,13 +2169,25 @@ ngx_http_fastcgi_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) if (f->type == NGX_HTTP_FASTCGI_STDOUT && f->length == 0) { f->state = ngx_http_fastcgi_st_padding; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0, + "http fastcgi closed stdout"); + + if (f->rest > 0) { + ngx_log_error(NGX_LOG_ERR, p->log, 0, + "upstream prematurely closed " + "FastCGI stdout"); + + p->upstream_error = 1; + p->upstream_eof = 0; + f->closed = 1; + + break; + } + if (!flcf->keep_conn) { p->upstream_done = 1; } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0, - "http fastcgi closed stdout"); - continue; } @@ -2152,6 +2196,18 @@ ngx_http_fastcgi_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0, "http fastcgi sent end request"); + if (f->rest > 0) { + ngx_log_error(NGX_LOG_ERR, p->log, 0, + "upstream prematurely closed " + "FastCGI request"); + + p->upstream_error = 1; + p->upstream_eof = 0; + f->closed = 1; + + break; + } + if (!flcf->keep_conn) { p->upstream_done = 1; break; @@ -2261,6 +2317,18 @@ ngx_http_fastcgi_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) break; } + if (f->rest == -2) { + f->rest = r->upstream->headers_in.content_length_n; + } + + if (f->rest == 0) { + ngx_log_error(NGX_LOG_WARN, p->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); + p->upstream_done = 1; + break; + } + cl = ngx_chain_get_free_buf(p->pool, &p->free); if (cl == NULL) { return NGX_ERROR; @@ -2298,15 +2366,27 @@ ngx_http_fastcgi_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) f->pos += f->length; b->last = f->pos; - continue; + } else { + f->length -= f->last - f->pos; + f->pos = f->last; + b->last = f->last; } - f->length -= f->last - f->pos; + if (f->rest > 0) { - b->last = f->last; + if (b->last - b->pos > f->rest) { + ngx_log_error(NGX_LOG_WARN, p->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); - break; + b->last = b->pos + f->rest; + p->upstream_done = 1; + break; + } + + f->rest -= b->last - b->pos; + } } if (flcf->keep_conn) { @@ -2400,6 +2480,14 @@ ngx_http_fastcgi_non_buffered_filter(void *data, ssize_t bytes) if (f->type == NGX_HTTP_FASTCGI_END_REQUEST) { + if (f->rest > 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream prematurely closed " + "FastCGI request"); + u->error = 1; + break; + } + if (f->pos + f->padding < f->last) { u->length = 0; break; @@ -2495,6 +2583,14 @@ ngx_http_fastcgi_non_buffered_filter(void *data, ssize_t bytes) break; } + if (f->rest == 0) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); + u->length = 0; + break; + } + cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs); if (cl == NULL) { return NGX_ERROR; @@ -2519,13 +2615,27 @@ ngx_http_fastcgi_non_buffered_filter(void *data, ssize_t bytes) f->pos += f->length; b->last = f->pos; - continue; + } else { + f->length -= f->last - f->pos; + f->pos = f->last; + b->last = f->last; } - f->length -= f->last - f->pos; - b->last = f->last; + if (f->rest > 0) { + + if (b->last - b->pos > f->rest) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); - break; + b->last = b->pos + f->rest; + u->length = 0; + + break; + } + + f->rest -= b->last - b->pos; + } } return NGX_OK; diff --git a/src/http/modules/ngx_http_flv_module.c b/src/http/modules/ngx_http_flv_module.c index 7b72fae033..cc06d538a7 100644 --- a/src/http/modules/ngx_http_flv_module.c +++ b/src/http/modules/ngx_http_flv_module.c @@ -156,12 +156,6 @@ ngx_http_flv_handler(ngx_http_request_t *r) } if (!of.is_file) { - - if (ngx_close_file(of.fd) == NGX_FILE_ERROR) { - ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, - ngx_close_file_n " \"%s\" failed", path.data); - } - return NGX_DECLINED; } diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c index 52a2e9e868..a25d36c22a 100644 --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -37,9 +37,7 @@ typedef struct { 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; + ngx_array_t *ssl_conf_commands; #if (T_NGX_SSL_NTLS) ngx_str_t enc_certificate; @@ -91,6 +89,8 @@ typedef struct { ngx_uint_t pings; ngx_uint_t settings; + off_t length; + ssize_t send_window; size_t recv_window; @@ -127,6 +127,8 @@ typedef struct { unsigned end_stream:1; unsigned done:1; unsigned status:1; + unsigned rst:1; + unsigned goaway:1; ngx_http_request_t *request; @@ -212,6 +214,8 @@ static char *ngx_http_grpc_pass(ngx_conf_t *cf, ngx_command_t *cmd, #if (NGX_HTTP_SSL) static char *ngx_http_grpc_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_grpc_ssl_conf_command_check(ngx_conf_t *cf, void *post, + void *data); static ngx_int_t ngx_http_grpc_set_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *glcf); #endif @@ -246,6 +250,9 @@ static ngx_conf_bitmask_t ngx_http_grpc_ssl_protocols[] = { { ngx_null_string, 0 } }; +static ngx_conf_post_t ngx_http_grpc_ssl_conf_command_post = + { ngx_http_grpc_ssl_conf_command_check }; + #endif @@ -423,16 +430,16 @@ static ngx_command_t ngx_http_grpc_commands[] = { { 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_set_complex_value_zero_slot, NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_grpc_loc_conf_t, ssl_certificate), + offsetof(ngx_http_grpc_loc_conf_t, upstream.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_set_complex_value_zero_slot, NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_grpc_loc_conf_t, ssl_certificate_key), + offsetof(ngx_http_grpc_loc_conf_t, upstream.ssl_certificate_key), NULL }, { ngx_string("grpc_ssl_password_file"), @@ -442,6 +449,13 @@ static ngx_command_t ngx_http_grpc_commands[] = { 0, NULL }, + { ngx_string("grpc_ssl_conf_command"), + 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, ssl_conf_commands), + &ngx_http_grpc_ssl_conf_command_post }, + #if (T_NGX_SSL_NTLS) { ngx_string("grpc_enable_ntls"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, @@ -1169,20 +1183,11 @@ ngx_http_grpc_create_request(ngx_http_request_t *r) 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 + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc header: %*xs%s, len: %uz", + (size_t) ngx_min(b->last - b->pos, 256), b->pos, + b->last - b->pos > 256 ? "..." : "", + b->last - b->pos); if (r->request_body_no_buffering) { @@ -1249,6 +1254,8 @@ ngx_http_grpc_reinit_request(ngx_http_request_t *r) ctx->end_stream = 0; ctx->done = 0; ctx->status = 0; + ctx->rst = 0; + ctx->goaway = 0; ctx->connection = NULL; return NGX_OK; @@ -1604,6 +1611,7 @@ ngx_http_grpc_body_output_filter(void *data, ngx_chain_t *in) && ctx->out == NULL && ctx->output_closed && !ctx->output_blocked + && !ctx->goaway && ctx->state == ngx_http_grpc_st_start) { u->keepalive = 1; @@ -1631,20 +1639,11 @@ ngx_http_grpc_process_header(ngx_http_request_t *r) 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 + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "grpc response: %*xs%s, len: %uz", + (size_t) ngx_min(b->last - b->pos, 256), + b->pos, b->last - b->pos > 256 ? "..." : "", + b->last - b->pos); ctx = ngx_http_grpc_get_ctx(r); @@ -1762,6 +1761,8 @@ ngx_http_grpc_process_header(ngx_http_request_t *r) return NGX_HTTP_UPSTREAM_INVALID_HEADER; } + ctx->goaway = 1; + continue; } @@ -1955,6 +1956,7 @@ ngx_http_grpc_process_header(ngx_http_request_t *r) && ctx->out == NULL && ctx->output_closed && !ctx->output_blocked + && !ctx->goaway && b->last == b->pos) { u->keepalive = 1; @@ -1995,10 +1997,29 @@ ngx_http_grpc_filter_init(void *data) r = ctx->request; u = r->upstream; - u->length = 1; + if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT + || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED + || r->method == NGX_HTTP_HEAD) + { + ctx->length = 0; + + } else { + ctx->length = u->headers_in.content_length_n; + } if (ctx->end_stream) { + + if (ctx->length > 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream prematurely closed stream"); + return NGX_ERROR; + } + u->length = 0; + ctx->done = 1; + + } else { + u->length = 1; } return NGX_OK; @@ -2041,6 +2062,12 @@ ngx_http_grpc_filter(void *data, ssize_t bytes) if (ctx->done) { + if (ctx->length > 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream prematurely closed stream"); + return NGX_ERROR; + } + /* * We have finished parsing the response and the * remaining control frames. If there are unsent @@ -2058,6 +2085,7 @@ ngx_http_grpc_filter(void *data, ssize_t bytes) if (ctx->in == NULL && ctx->output_closed && !ctx->output_blocked + && !ctx->goaway && ctx->state == ngx_http_grpc_st_start) { u->keepalive = 1; @@ -2132,7 +2160,10 @@ ngx_http_grpc_filter(void *data, ssize_t bytes) return NGX_ERROR; } - if (ctx->stream_id && ctx->done) { + if (ctx->stream_id && ctx->done + && ctx->type != NGX_HTTP_V2_RST_STREAM_FRAME + && ctx->type != NGX_HTTP_V2_WINDOW_UPDATE_FRAME) + { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent frame for closed stream %ui", ctx->stream_id); @@ -2175,11 +2206,23 @@ ngx_http_grpc_filter(void *data, ssize_t bytes) return NGX_ERROR; } - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "upstream rejected request with error %ui", - ctx->error); + if (ctx->error || !ctx->done) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream rejected request with error %ui", + ctx->error); + return NGX_ERROR; + } - return NGX_ERROR; + if (ctx->rst) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent frame for closed stream %ui", + ctx->stream_id); + return NGX_ERROR; + } + + ctx->rst = 1; + + continue; } if (ctx->type == NGX_HTTP_V2_GOAWAY_FRAME) { @@ -2214,6 +2257,8 @@ ngx_http_grpc_filter(void *data, ssize_t bytes) return NGX_ERROR; } + ctx->goaway = 1; + continue; } @@ -2446,6 +2491,18 @@ ngx_http_grpc_filter(void *data, ssize_t bytes) b->pos = b->last; buf->last = b->pos; + if (ctx->length != -1) { + + if (buf->last - buf->pos > ctx->length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent response body larger " + "than indicated content length"); + return NGX_ERROR; + } + + ctx->length -= buf->last - buf->pos; + } + return NGX_AGAIN; } @@ -2453,6 +2510,18 @@ ngx_http_grpc_filter(void *data, ssize_t bytes) buf->last = b->pos; ctx->rest = ctx->padding; + if (ctx->length != -1) { + + if (buf->last - buf->pos > ctx->length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent response body larger " + "than indicated content length"); + return NGX_ERROR; + } + + ctx->length -= buf->last - buf->pos; + } + done: if (ctx->padding) { @@ -3155,10 +3224,10 @@ ngx_http_grpc_parse_fragment(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, 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) + if (ngx_http_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, @@ -3264,10 +3333,10 @@ ngx_http_grpc_parse_fragment(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, 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) + if (ngx_http_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, @@ -3359,7 +3428,7 @@ ngx_http_grpc_validate_header_name(ngx_http_request_t *r, ngx_str_t *s) return NGX_ERROR; } - if (ch == '\0' || ch == CR || ch == LF) { + if (ch <= 0x20 || ch == 0x7f) { return NGX_ERROR; } } @@ -3461,6 +3530,8 @@ ngx_http_grpc_parse_rst_stream(ngx_http_request_t *r, ngx_http_grpc_ctx_t *ctx, return NGX_AGAIN; } + ctx->state = ngx_http_grpc_st_start; + return NGX_OK; } @@ -4314,9 +4385,7 @@ ngx_http_grpc_create_loc_conf(ngx_conf_t *cf) * 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 }; @@ -4327,8 +4396,6 @@ ngx_http_grpc_create_loc_conf(ngx_conf_t *cf) * 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; @@ -4348,10 +4415,14 @@ ngx_http_grpc_create_loc_conf(ngx_conf_t *cf) #if (NGX_HTTP_SSL) conf->upstream.ssl_session_reuse = NGX_CONF_UNSET; + conf->upstream.ssl_name = NGX_CONF_UNSET_PTR; 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; + conf->upstream.ssl_certificate = NGX_CONF_UNSET_PTR; + conf->upstream.ssl_certificate_key = NGX_CONF_UNSET_PTR; + conf->upstream.ssl_passwords = NGX_CONF_UNSET_PTR; + conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; #if (T_NGX_SSL_NTLS) conf->upstream.tls_method = NULL; @@ -4374,6 +4445,8 @@ ngx_http_grpc_create_loc_conf(ngx_conf_t *cf) conf->upstream.pass_trailers = 1; conf->upstream.preserve_output = 1; + conf->headers_source = NGX_CONF_UNSET_PTR; + ngx_str_set(&conf->upstream.module, "grpc"); return conf; @@ -4445,10 +4518,8 @@ ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 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_ptr_value(conf->upstream.ssl_name, + prev->upstream.ssl_name, NULL); ngx_conf_merge_value(conf->upstream.ssl_server_name, prev->upstream.ssl_server_name, 0); ngx_conf_merge_value(conf->upstream.ssl_verify, @@ -4459,11 +4530,15 @@ ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 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); + ngx_conf_merge_ptr_value(conf->upstream.ssl_certificate, + prev->upstream.ssl_certificate, NULL); + ngx_conf_merge_ptr_value(conf->upstream.ssl_certificate_key, + prev->upstream.ssl_certificate_key, NULL); + ngx_conf_merge_ptr_value(conf->upstream.ssl_passwords, + prev->upstream.ssl_passwords, NULL); + + ngx_conf_merge_ptr_value(conf->ssl_conf_commands, + prev->ssl_conf_commands, NULL); if (conf->ssl && ngx_http_grpc_set_ssl(cf, conf) != NGX_OK) { return NGX_CONF_ERROR; @@ -4518,9 +4593,10 @@ ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) clcf->handler = ngx_http_grpc_handler; } - if (conf->headers_source == NULL) { + ngx_conf_merge_ptr_value(conf->headers_source, prev->headers_source, NULL); + + if (conf->headers_source == prev->headers_source) { conf->headers = prev->headers; - conf->headers_source = prev->headers_source; conf->host_set = prev->host_set; } @@ -4829,15 +4905,15 @@ ngx_http_grpc_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_str_t *value; - if (glcf->ssl_passwords != NGX_CONF_UNSET_PTR) { + if (glcf->upstream.ssl_passwords != NGX_CONF_UNSET_PTR) { return "is duplicate"; } value = cf->args->elts; - glcf->ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]); + glcf->upstream.ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]); - if (glcf->ssl_passwords == NULL) { + if (glcf->upstream.ssl_passwords == NULL) { return NGX_CONF_ERROR; } @@ -4845,6 +4921,17 @@ ngx_http_grpc_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } +static char * +ngx_http_grpc_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) +{ +#ifndef SSL_CONF_FLAG_FILE + return "is not supported on this platform"; +#else + return NGX_CONF_OK; +#endif +} + + static ngx_int_t ngx_http_grpc_set_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *glcf) { @@ -4872,26 +4959,48 @@ ngx_http_grpc_set_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *glcf) cln->handler = ngx_ssl_cleanup_ctx; cln->data = glcf->upstream.ssl; - if (glcf->ssl_certificate.len) { + if (ngx_ssl_ciphers(cf, glcf->upstream.ssl, &glcf->ssl_ciphers, 0) + != NGX_OK) + { + return NGX_ERROR; + } - if (glcf->ssl_certificate_key.len == 0) { + if (glcf->upstream.ssl_certificate) { + + if (glcf->upstream.ssl_certificate_key == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"grpc_ssl_certificate_key\" is defined " - "for certificate \"%V\"", &glcf->ssl_certificate); + "for certificate \"%V\"", + &glcf->upstream.ssl_certificate->value); return NGX_ERROR; } + if (glcf->upstream.ssl_certificate->lengths + || glcf->upstream.ssl_certificate_key->lengths) + { + glcf->upstream.ssl_passwords = + ngx_ssl_preserve_passwords(cf, glcf->upstream.ssl_passwords); + if (glcf->upstream.ssl_passwords == NULL) { + return NGX_ERROR; + } + + } else { #if (T_NGX_SSL_NTLS) - if (ngx_ssl_certificate(cf, glcf->upstream.ssl, &glcf->ssl_certificate, - &glcf->ssl_certificate_key, glcf->ssl_passwords, - SSL_NORMAL_CERT) + if (ngx_ssl_certificate(cf, glcf->upstream.ssl, + &glcf->upstream.ssl_certificate->value, + &glcf->upstream.ssl_certificate_key->value, + glcf->upstream.ssl_passwords, + SSL_NORMAL_CERT) #else - if (ngx_ssl_certificate(cf, glcf->upstream.ssl, &glcf->ssl_certificate, - &glcf->ssl_certificate_key, glcf->ssl_passwords) + if (ngx_ssl_certificate(cf, glcf->upstream.ssl, + &glcf->upstream.ssl_certificate->value, + &glcf->upstream.ssl_certificate_key->value, + glcf->upstream.ssl_passwords) #endif - != NGX_OK) - { - return NGX_ERROR; + != NGX_OK) + { + return NGX_ERROR; + } } } @@ -4934,12 +5043,6 @@ ngx_http_grpc_set_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *glcf) } #endif - 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, @@ -4980,6 +5083,12 @@ ngx_http_grpc_set_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *glcf) #endif + if (ngx_ssl_conf_commands(cf, glcf->upstream.ssl, glcf->ssl_conf_commands) + != NGX_OK) + { + return NGX_ERROR; + } + return NGX_OK; } diff --git a/src/http/modules/ngx_http_gzip_filter_module.c b/src/http/modules/ngx_http_gzip_filter_module.c index 241a93c361..a7e8685a03 100644 --- a/src/http/modules/ngx_http_gzip_filter_module.c +++ b/src/http/modules/ngx_http_gzip_filter_module.c @@ -59,7 +59,7 @@ typedef struct { unsigned done:1; unsigned nomem:1; unsigned buffering:1; - unsigned intel:1; + unsigned zlib_ng:1; size_t zin; size_t zout; @@ -225,7 +225,7 @@ static ngx_str_t ngx_http_gzip_ratio = ngx_string("gzip_ratio"); static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; -static ngx_uint_t ngx_http_gzip_assume_intel; +static ngx_uint_t ngx_http_gzip_assume_zlib_ng; static ngx_int_t @@ -518,18 +518,21 @@ ngx_http_gzip_filter_memory(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) * 8K is for zlib deflate_state, it takes * *) 5816 bytes on i386 and sparc64 (32-bit mode) * *) 5920 bytes on amd64 and sparc64 + * + * A zlib variant from Intel (https://github.com/jtkukunas/zlib) + * uses additional 16-byte padding in one of window-sized buffers. */ - if (!ngx_http_gzip_assume_intel) { - ctx->allocated = 8192 + (1 << (wbits + 2)) + (1 << (memlevel + 9)); + if (!ngx_http_gzip_assume_zlib_ng) { + ctx->allocated = 8192 + 16 + (1 << (wbits + 2)) + + (1 << (memlevel + 9)); } else { /* - * A zlib variant from Intel, https://github.com/jtkukunas/zlib. - * It can force window bits to 13 for fast compression level, - * on processors with SSE 4.2 it uses 64K hash instead of scaling - * it from the specified memory level, and also introduces - * 16-byte padding in one out of the two window-sized buffers. + * Another zlib variant, https://github.com/zlib-ng/zlib-ng. + * It forces window bits to 13 for fast compression level, + * uses 16-byte padding in one of window-sized buffers, and + * uses 128K hash. */ if (conf->level == 1) { @@ -537,9 +540,8 @@ ngx_http_gzip_filter_memory(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) } ctx->allocated = 8192 + 16 + (1 << (wbits + 2)) - + (1 << (ngx_max(memlevel, 8) + 8)) - + (1 << (memlevel + 8)); - ctx->intel = 1; + + 131072 + (1 << (memlevel + 8)); + ctx->zlib_ng = 1; } } @@ -962,13 +964,13 @@ ngx_http_gzip_filter_alloc(void *opaque, u_int items, u_int size) return p; } - if (ctx->intel) { + if (ctx->zlib_ng) { ngx_log_error(NGX_LOG_ALERT, ctx->request->connection->log, 0, "gzip filter failed to use preallocated memory: " "%ud of %ui", items * size, ctx->allocated); } else { - ngx_http_gzip_assume_intel = 1; + ngx_http_gzip_assume_zlib_ng = 1; } p = ngx_palloc(ctx->request->pool, items * size); diff --git a/src/http/modules/ngx_http_limit_req_module.c b/src/http/modules/ngx_http_limit_req_module.c index 7f2a00d849..3408d79ed8 100644 --- a/src/http/modules/ngx_http_limit_req_module.c +++ b/src/http/modules/ngx_http_limit_req_module.c @@ -9,6 +9,7 @@ #include #include + #define NGX_HTTP_LIMIT_REQ_PASSED 1 #define NGX_HTTP_LIMIT_REQ_DELAYED 2 #define NGX_HTTP_LIMIT_REQ_REJECTED 3 @@ -87,6 +88,8 @@ static ngx_int_t ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, ngx_uint_t hash, ngx_str_t *key, ngx_uint_t *ep, ngx_uint_t account); static ngx_msec_t ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits, ngx_uint_t n, ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit); +static void ngx_http_limit_req_unlock(ngx_http_limit_req_limit_t *limits, + ngx_uint_t n); static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n); @@ -358,6 +361,7 @@ ngx_http_limit_req_handler(ngx_http_request_t *r) #endif if (ngx_http_complex_value(r, &ctx->key, &key) != NGX_OK) { + ngx_http_limit_req_unlock(limits, n); return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -405,21 +409,7 @@ ngx_http_limit_req_handler(ngx_http_request_t *r) &limit->shm_zone->shm.name); } - while (n--) { - ctx = limits[n].shm_zone->data; - - if (ctx->node == NULL) { - continue; - } - - ngx_shmtx_lock(&ctx->shpool->mutex); - - ctx->node->count--; - - ngx_shmtx_unlock(&ctx->shpool->mutex); - - ctx->node = NULL; - } + ngx_http_limit_req_unlock(limits, n); if (lrcf->dry_run) { r->main->limit_req_status = NGX_HTTP_LIMIT_REQ_REJECTED_DRY_RUN; @@ -428,6 +418,8 @@ ngx_http_limit_req_handler(ngx_http_request_t *r) #if (T_LIMIT_REQ) if (rc == NGX_ERROR || limit->forbid_action.len == 0) { + r->main->limit_req_status = NGX_HTTP_LIMIT_REQ_REJECTED; + return lrcf->status_code; } else if (limit->forbid_action.data[0] == '@') { @@ -451,7 +443,8 @@ ngx_http_limit_req_handler(ngx_http_request_t *r) return NGX_DONE; #else r->main->limit_req_status = NGX_HTTP_LIMIT_REQ_REJECTED; - return lrcf->status_code; + + return lrcf->status_code; #endif } @@ -480,8 +473,13 @@ ngx_http_limit_req_handler(ngx_http_request_t *r) r->main->limit_req_status = NGX_HTTP_LIMIT_REQ_DELAYED; - if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) { - return NGX_HTTP_INTERNAL_SERVER_ERROR; + if (r->connection->read->ready) { + ngx_post_event(r->connection->read, &ngx_posted_events); + + } else { + if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } } r->read_event_handler = ngx_http_test_reading; @@ -771,6 +769,29 @@ ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits, ngx_uint_t n, } +static void +ngx_http_limit_req_unlock(ngx_http_limit_req_limit_t *limits, ngx_uint_t n) +{ + ngx_http_limit_req_ctx_t *ctx; + + while (n--) { + ctx = limits[n].shm_zone->data; + + if (ctx->node == NULL) { + continue; + } + + ngx_shmtx_lock(&ctx->shpool->mutex); + + ctx->node->count--; + + ngx_shmtx_unlock(&ctx->shpool->mutex); + + ctx->node = NULL; + } +} + + static void ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n) { diff --git a/src/http/modules/ngx_http_memcached_module.c b/src/http/modules/ngx_http_memcached_module.c index ee4b1d895e..eb10f3a0fd 100644 --- a/src/http/modules/ngx_http_memcached_module.c +++ b/src/http/modules/ngx_http_memcached_module.c @@ -494,10 +494,11 @@ ngx_http_memcached_filter(void *data, ssize_t bytes) if (u->length == (ssize_t) ctx->rest) { - if (ngx_strncmp(b->last, + if (bytes > u->length + || ngx_strncmp(b->last, ngx_http_memcached_end + NGX_HTTP_MEMCACHED_END - ctx->rest, bytes) - != 0) + != 0) { ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0, "memcached sent invalid trailer"); @@ -549,7 +550,9 @@ ngx_http_memcached_filter(void *data, ssize_t bytes) last += (size_t) (u->length - NGX_HTTP_MEMCACHED_END); - if (ngx_strncmp(last, ngx_http_memcached_end, b->last - last) != 0) { + if (bytes > u->length + || ngx_strncmp(last, ngx_http_memcached_end, b->last - last) != 0) + { ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0, "memcached sent invalid trailer"); diff --git a/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c index 4893f59d0a..4eff01e9c2 100644 --- a/src/http/modules/ngx_http_mp4_module.c +++ b/src/http/modules/ngx_http_mp4_module.c @@ -11,31 +11,33 @@ #define NGX_HTTP_MP4_TRAK_ATOM 0 #define NGX_HTTP_MP4_TKHD_ATOM 1 -#define NGX_HTTP_MP4_MDIA_ATOM 2 -#define NGX_HTTP_MP4_MDHD_ATOM 3 -#define NGX_HTTP_MP4_HDLR_ATOM 4 -#define NGX_HTTP_MP4_MINF_ATOM 5 -#define NGX_HTTP_MP4_VMHD_ATOM 6 -#define NGX_HTTP_MP4_SMHD_ATOM 7 -#define NGX_HTTP_MP4_DINF_ATOM 8 -#define NGX_HTTP_MP4_STBL_ATOM 9 -#define NGX_HTTP_MP4_STSD_ATOM 10 -#define NGX_HTTP_MP4_STTS_ATOM 11 -#define NGX_HTTP_MP4_STTS_DATA 12 -#define NGX_HTTP_MP4_STSS_ATOM 13 -#define NGX_HTTP_MP4_STSS_DATA 14 -#define NGX_HTTP_MP4_CTTS_ATOM 15 -#define NGX_HTTP_MP4_CTTS_DATA 16 -#define NGX_HTTP_MP4_STSC_ATOM 17 -#define NGX_HTTP_MP4_STSC_START 18 -#define NGX_HTTP_MP4_STSC_DATA 19 -#define NGX_HTTP_MP4_STSC_END 20 -#define NGX_HTTP_MP4_STSZ_ATOM 21 -#define NGX_HTTP_MP4_STSZ_DATA 22 -#define NGX_HTTP_MP4_STCO_ATOM 23 -#define NGX_HTTP_MP4_STCO_DATA 24 -#define NGX_HTTP_MP4_CO64_ATOM 25 -#define NGX_HTTP_MP4_CO64_DATA 26 +#define NGX_HTTP_MP4_EDTS_ATOM 2 +#define NGX_HTTP_MP4_ELST_ATOM 3 +#define NGX_HTTP_MP4_MDIA_ATOM 4 +#define NGX_HTTP_MP4_MDHD_ATOM 5 +#define NGX_HTTP_MP4_HDLR_ATOM 6 +#define NGX_HTTP_MP4_MINF_ATOM 7 +#define NGX_HTTP_MP4_VMHD_ATOM 8 +#define NGX_HTTP_MP4_SMHD_ATOM 9 +#define NGX_HTTP_MP4_DINF_ATOM 10 +#define NGX_HTTP_MP4_STBL_ATOM 11 +#define NGX_HTTP_MP4_STSD_ATOM 12 +#define NGX_HTTP_MP4_STTS_ATOM 13 +#define NGX_HTTP_MP4_STTS_DATA 14 +#define NGX_HTTP_MP4_STSS_ATOM 15 +#define NGX_HTTP_MP4_STSS_DATA 16 +#define NGX_HTTP_MP4_CTTS_ATOM 17 +#define NGX_HTTP_MP4_CTTS_DATA 18 +#define NGX_HTTP_MP4_STSC_ATOM 19 +#define NGX_HTTP_MP4_STSC_START 20 +#define NGX_HTTP_MP4_STSC_DATA 21 +#define NGX_HTTP_MP4_STSC_END 22 +#define NGX_HTTP_MP4_STSZ_ATOM 23 +#define NGX_HTTP_MP4_STSZ_DATA 24 +#define NGX_HTTP_MP4_STCO_ATOM 25 +#define NGX_HTTP_MP4_STCO_DATA 26 +#define NGX_HTTP_MP4_CO64_ATOM 27 +#define NGX_HTTP_MP4_CO64_DATA 28 #define NGX_HTTP_MP4_LAST_ATOM NGX_HTTP_MP4_CO64_DATA @@ -43,6 +45,7 @@ typedef struct { size_t buffer_size; size_t max_buffer_size; + ngx_flag_t start_key_frame; } ngx_http_mp4_conf_t; @@ -53,6 +56,25 @@ typedef struct { } ngx_mp4_stsc_entry_t; +typedef struct { + u_char size[4]; + u_char name[4]; +} ngx_mp4_edts_atom_t; + + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char entries[4]; + u_char duration[8]; + u_char media_time[8]; + u_char media_rate[2]; + u_char reserved[2]; +} ngx_mp4_elst_atom_t; + + typedef struct { uint32_t timescale; uint32_t time_to_sample_entries; @@ -70,6 +92,9 @@ typedef struct { ngx_uint_t end_chunk_samples; uint64_t start_chunk_samples_size; uint64_t end_chunk_samples_size; + uint64_t duration; + uint64_t prefix; + uint64_t movie_duration; off_t start_offset; off_t end_offset; @@ -85,6 +110,8 @@ typedef struct { ngx_buf_t trak_atom_buf; ngx_buf_t tkhd_atom_buf; + ngx_buf_t edts_atom_buf; + ngx_buf_t elst_atom_buf; ngx_buf_t mdia_atom_buf; ngx_buf_t mdhd_atom_buf; ngx_buf_t hdlr_atom_buf; @@ -111,6 +138,8 @@ typedef struct { ngx_buf_t co64_atom_buf; ngx_buf_t co64_data_buf; + ngx_mp4_edts_atom_t edts_atom; + ngx_mp4_elst_atom_t elst_atom; ngx_mp4_stsc_entry_t stsc_start_chunk_entry; ngx_mp4_stsc_entry_t stsc_end_chunk_entry; } ngx_http_mp4_trak_t; @@ -186,6 +215,14 @@ typedef struct { ((u_char *) (p))[6] = n3; \ ((u_char *) (p))[7] = n4 +#define ngx_mp4_get_16value(p) \ + ( ((uint16_t) ((u_char *) (p))[0] << 8) \ + + ( ((u_char *) (p))[1]) ) + +#define ngx_mp4_set_16value(p, n) \ + ((u_char *) (p))[0] = (u_char) ((n) >> 8); \ + ((u_char *) (p))[1] = (u_char) (n) + #define ngx_mp4_get_32value(p) \ ( ((uint32_t) ((u_char *) (p))[0] << 24) \ + ( ((u_char *) (p))[1] << 16) \ @@ -253,6 +290,8 @@ static void ngx_http_mp4_update_mdia_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static ngx_int_t ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); +static void ngx_http_mp4_update_mdhd_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak); static ngx_int_t ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_minf_atom(ngx_http_mp4_file_t *mp4, @@ -267,6 +306,8 @@ static ngx_int_t ngx_http_mp4_read_smhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); +static void ngx_http_mp4_update_edts_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak); static void ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static ngx_int_t ngx_http_mp4_read_stsd_atom(ngx_http_mp4_file_t *mp4, @@ -277,6 +318,8 @@ static ngx_int_t ngx_http_mp4_update_stts_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak); static ngx_int_t ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak, ngx_uint_t start); +static uint32_t ngx_http_mp4_seek_key_frame(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak, uint32_t start_sample); static ngx_int_t ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size); static ngx_int_t ngx_http_mp4_update_stss_atom(ngx_http_mp4_file_t *mp4, @@ -340,6 +383,13 @@ static ngx_command_t ngx_http_mp4_commands[] = { offsetof(ngx_http_mp4_conf_t, max_buffer_size), NULL }, + { ngx_string("mp4_start_key_frame"), + 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_mp4_conf_t, start_key_frame), + NULL }, + ngx_null_command }; @@ -521,12 +571,6 @@ ngx_http_mp4_handler(ngx_http_request_t *r) } if (!of.is_file) { - - if (ngx_close_file(of.fd) == NGX_FILE_ERROR) { - ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, - ngx_close_file_n " \"%s\" failed", path.data); - } - return NGX_DECLINED; } @@ -828,10 +872,11 @@ ngx_http_mp4_process(ngx_http_mp4_file_t *mp4) ngx_http_mp4_update_stbl_atom(mp4, &trak[i]); ngx_http_mp4_update_minf_atom(mp4, &trak[i]); - trak[i].size += trak[i].mdhd_size; + ngx_http_mp4_update_mdhd_atom(mp4, &trak[i]); trak[i].size += trak[i].hdlr_size; ngx_http_mp4_update_mdia_atom(mp4, &trak[i]); trak[i].size += trak[i].tkhd_size; + ngx_http_mp4_update_edts_atom(mp4, &trak[i]); ngx_http_mp4_update_trak_atom(mp4, &trak[i]); mp4->moov_size += trak[i].size; @@ -1624,6 +1669,7 @@ ngx_http_mp4_read_tkhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) } trak->tkhd_size = atom_size; + trak->movie_duration = duration; ngx_mp4_set_32value(tkhd_atom->size, atom_size); @@ -1799,16 +1845,10 @@ ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) trak->mdhd_size = atom_size; trak->timescale = timescale; + trak->duration = duration; ngx_mp4_set_32value(mdhd_atom->size, atom_size); - if (mdhd_atom->version[0] == 0) { - ngx_mp4_set_32value(mdhd_atom->duration, duration); - - } else { - ngx_mp4_set_64value(mdhd64_atom->duration, duration); - } - atom = &trak->mdhd_atom_buf; atom->temporary = 1; atom->pos = atom_header; @@ -1822,6 +1862,33 @@ ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) } +static void +ngx_http_mp4_update_mdhd_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak) +{ + ngx_buf_t *atom; + ngx_mp4_mdhd_atom_t *mdhd_atom; + ngx_mp4_mdhd64_atom_t *mdhd64_atom; + + atom = trak->out[NGX_HTTP_MP4_MDHD_ATOM].buf; + if (atom == NULL) { + return; + } + + mdhd_atom = (ngx_mp4_mdhd_atom_t *) atom->pos; + mdhd64_atom = (ngx_mp4_mdhd64_atom_t *) atom->pos; + + if (mdhd_atom->version[0] == 0) { + ngx_mp4_set_32value(mdhd_atom->duration, trak->duration); + + } else { + ngx_mp4_set_64value(mdhd64_atom->duration, trak->duration); + } + + trak->size += trak->mdhd_size; +} + + static ngx_int_t ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) { @@ -2053,6 +2120,59 @@ ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) } +static void +ngx_http_mp4_update_edts_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak) +{ + ngx_buf_t *atom; + ngx_mp4_elst_atom_t *elst_atom; + ngx_mp4_edts_atom_t *edts_atom; + + if (trak->prefix == 0) { + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 edts atom update prefix:%uL", trak->prefix); + + edts_atom = &trak->edts_atom; + ngx_mp4_set_32value(edts_atom->size, sizeof(ngx_mp4_edts_atom_t) + + sizeof(ngx_mp4_elst_atom_t)); + ngx_mp4_set_atom_name(edts_atom, 'e', 'd', 't', 's'); + + atom = &trak->edts_atom_buf; + atom->temporary = 1; + atom->pos = (u_char *) edts_atom; + atom->last = (u_char *) edts_atom + sizeof(ngx_mp4_edts_atom_t); + + trak->out[NGX_HTTP_MP4_EDTS_ATOM].buf = atom; + + elst_atom = &trak->elst_atom; + ngx_mp4_set_32value(elst_atom->size, sizeof(ngx_mp4_elst_atom_t)); + ngx_mp4_set_atom_name(elst_atom, 'e', 'l', 's', 't'); + + elst_atom->version[0] = 1; + elst_atom->flags[0] = 0; + elst_atom->flags[1] = 0; + elst_atom->flags[2] = 0; + + ngx_mp4_set_32value(elst_atom->entries, 1); + ngx_mp4_set_64value(elst_atom->duration, trak->movie_duration); + ngx_mp4_set_64value(elst_atom->media_time, trak->prefix); + ngx_mp4_set_16value(elst_atom->media_rate, 1); + ngx_mp4_set_16value(elst_atom->reserved, 0); + + atom = &trak->elst_atom_buf; + atom->temporary = 1; + atom->pos = (u_char *) elst_atom; + atom->last = (u_char *) elst_atom + sizeof(ngx_mp4_elst_atom_t); + + trak->out[NGX_HTTP_MP4_ELST_ATOM].buf = atom; + + trak->size += sizeof(ngx_mp4_edts_atom_t) + sizeof(ngx_mp4_elst_atom_t); +} + + static void ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak) @@ -2264,7 +2384,7 @@ static ngx_int_t ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak, ngx_uint_t start) { - uint32_t count, duration, rest; + uint32_t count, duration, rest, key_prefix; uint64_t start_time; ngx_buf_t *data; ngx_uint_t start_sample, entries, start_sec; @@ -2288,7 +2408,7 @@ ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4, data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf; - start_time = (uint64_t) start_sec * trak->timescale / 1000; + start_time = (uint64_t) start_sec * trak->timescale / 1000 + trak->prefix; entries = trak->time_to_sample_entries; start_sample = 0; @@ -2334,6 +2454,26 @@ ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4, found: if (start) { + key_prefix = ngx_http_mp4_seek_key_frame(mp4, trak, start_sample); + + start_sample -= key_prefix; + + while (rest < key_prefix) { + trak->prefix += rest * duration; + key_prefix -= rest; + + entry--; + entries++; + + count = ngx_mp4_get_32value(entry->count); + duration = ngx_mp4_get_32value(entry->duration); + rest = count; + } + + trak->prefix += key_prefix * duration; + trak->duration += trak->prefix; + rest -= key_prefix; + ngx_mp4_set_32value(entry->count, count - rest); data->pos = (u_char *) entry; trak->time_to_sample_entries = entries; @@ -2358,6 +2498,49 @@ ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4, } +static uint32_t +ngx_http_mp4_seek_key_frame(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak, + uint32_t start_sample) +{ + uint32_t key_prefix, sample, *entry, *end; + ngx_buf_t *data; + ngx_http_mp4_conf_t *conf; + + conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module); + if (!conf->start_key_frame) { + return 0; + } + + data = trak->out[NGX_HTTP_MP4_STSS_DATA].buf; + if (data == NULL) { + return 0; + } + + entry = (uint32_t *) data->pos; + end = (uint32_t *) data->last; + + /* sync samples starts from 1 */ + start_sample++; + + key_prefix = 0; + + while (entry < end) { + sample = ngx_mp4_get_32value(entry); + if (sample > start_sample) { + break; + } + + key_prefix = start_sample - sample; + entry++; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 key frame prefix:%uD", key_prefix); + + return key_prefix; +} + + typedef struct { u_char size[4]; u_char name[4]; @@ -3743,6 +3926,7 @@ ngx_http_mp4_create_conf(ngx_conf_t *cf) conf->buffer_size = NGX_CONF_UNSET_SIZE; conf->max_buffer_size = NGX_CONF_UNSET_SIZE; + conf->start_key_frame = NGX_CONF_UNSET; return conf; } @@ -3757,6 +3941,7 @@ ngx_http_mp4_merge_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 512 * 1024); ngx_conf_merge_size_value(conf->max_buffer_size, prev->max_buffer_size, 10 * 1024 * 1024); + ngx_conf_merge_value(conf->start_key_frame, prev->start_key_frame, 0); return NGX_CONF_OK; } diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index 7f88488e1d..15e314b0c5 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -10,6 +10,19 @@ #include +#define NGX_HTTP_PROXY_COOKIE_SECURE 0x0001 +#define NGX_HTTP_PROXY_COOKIE_SECURE_ON 0x0002 +#define NGX_HTTP_PROXY_COOKIE_SECURE_OFF 0x0004 +#define NGX_HTTP_PROXY_COOKIE_HTTPONLY 0x0008 +#define NGX_HTTP_PROXY_COOKIE_HTTPONLY_ON 0x0010 +#define NGX_HTTP_PROXY_COOKIE_HTTPONLY_OFF 0x0020 +#define NGX_HTTP_PROXY_COOKIE_SAMESITE 0x0040 +#define NGX_HTTP_PROXY_COOKIE_SAMESITE_STRICT 0x0080 +#define NGX_HTTP_PROXY_COOKIE_SAMESITE_LAX 0x0100 +#define NGX_HTTP_PROXY_COOKIE_SAMESITE_NONE 0x0200 +#define NGX_HTTP_PROXY_COOKIE_SAMESITE_OFF 0x0400 + + typedef struct { ngx_array_t caches; /* ngx_http_file_cache_t * */ } ngx_http_proxy_main_conf_t; @@ -18,7 +31,7 @@ typedef struct { typedef struct ngx_http_proxy_rewrite_s ngx_http_proxy_rewrite_t; typedef ngx_int_t (*ngx_http_proxy_rewrite_pt)(ngx_http_request_t *r, - ngx_table_elt_t *h, size_t prefix, size_t len, + ngx_str_t *value, size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr); struct ngx_http_proxy_rewrite_s { @@ -35,6 +48,19 @@ struct ngx_http_proxy_rewrite_s { }; +typedef struct { + union { + ngx_http_complex_value_t complex; +#if (NGX_PCRE) + ngx_http_regex_t *regex; +#endif + } cookie; + + ngx_array_t flags_values; + ngx_uint_t regex; +} ngx_http_proxy_cookie_flags_t; + + typedef struct { ngx_str_t key_start; ngx_str_t schema; @@ -72,6 +98,7 @@ typedef struct { ngx_array_t *redirects; ngx_array_t *cookie_domains; ngx_array_t *cookie_paths; + ngx_array_t *cookie_flags; ngx_http_complex_value_t *method; ngx_str_t location; @@ -97,9 +124,7 @@ typedef struct { 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; + ngx_array_t *ssl_conf_commands; #if (T_NGX_SSL_NTLS) ngx_str_t enc_certificate; @@ -165,10 +190,16 @@ static ngx_int_t ngx_http_proxy_rewrite_redirect(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix); static ngx_int_t ngx_http_proxy_rewrite_cookie(ngx_http_request_t *r, ngx_table_elt_t *h); +static ngx_int_t ngx_http_proxy_parse_cookie(ngx_str_t *value, + ngx_array_t *attrs); static ngx_int_t ngx_http_proxy_rewrite_cookie_value(ngx_http_request_t *r, - ngx_table_elt_t *h, u_char *value, ngx_array_t *rewrites); + ngx_str_t *value, ngx_array_t *rewrites); +static ngx_int_t ngx_http_proxy_rewrite_cookie_flags(ngx_http_request_t *r, + ngx_array_t *attrs, ngx_array_t *flags); +static ngx_int_t ngx_http_proxy_edit_cookie_flags(ngx_http_request_t *r, + ngx_array_t *attrs, ngx_uint_t flags); static ngx_int_t ngx_http_proxy_rewrite(ngx_http_request_t *r, - ngx_table_elt_t *h, size_t prefix, size_t len, ngx_str_t *replacement); + ngx_str_t *value, size_t prefix, size_t len, ngx_str_t *replacement); static ngx_int_t ngx_http_proxy_add_variables(ngx_conf_t *cf); static void *ngx_http_proxy_create_main_conf(ngx_conf_t *cf); @@ -187,6 +218,8 @@ static char *ngx_http_proxy_cookie_domain(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_proxy_cookie_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_proxy_cookie_flags(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static char *ngx_http_proxy_store(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #if (NGX_HTTP_CACHE) @@ -201,6 +234,10 @@ static char *ngx_http_proxy_ssl_password_file(ngx_conf_t *cf, #endif static char *ngx_http_proxy_lowat_check(ngx_conf_t *cf, void *post, void *data); +#if (NGX_HTTP_SSL) +static char *ngx_http_proxy_ssl_conf_command_check(ngx_conf_t *cf, void *post, + void *data); +#endif static ngx_int_t ngx_http_proxy_rewrite_regex(ngx_conf_t *cf, ngx_http_proxy_rewrite_t *pr, ngx_str_t *regex, ngx_uint_t caseless); @@ -246,6 +283,9 @@ static ngx_conf_bitmask_t ngx_http_proxy_ssl_protocols[] = { { ngx_null_string, 0 } }; +static ngx_conf_post_t ngx_http_proxy_ssl_conf_command_post = + { ngx_http_proxy_ssl_conf_command_check }; + #endif @@ -289,6 +329,13 @@ static ngx_command_t ngx_http_proxy_commands[] = { 0, NULL }, + { ngx_string("proxy_cookie_flags"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234, + ngx_http_proxy_cookie_flags, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + { ngx_string("proxy_store"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_proxy_store, @@ -719,16 +766,16 @@ static ngx_command_t ngx_http_proxy_commands[] = { { ngx_string("proxy_ssl_certificate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_conf_set_str_slot, + ngx_http_set_complex_value_zero_slot, NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_proxy_loc_conf_t, ssl_certificate), + offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_certificate), NULL }, { ngx_string("proxy_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_set_complex_value_zero_slot, NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_proxy_loc_conf_t, ssl_certificate_key), + offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_certificate_key), NULL }, { ngx_string("proxy_ssl_password_file"), @@ -738,6 +785,13 @@ static ngx_command_t ngx_http_proxy_commands[] = { 0, NULL }, + { ngx_string("proxy_ssl_conf_command"), + 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_proxy_loc_conf_t, ssl_conf_commands), + &ngx_http_proxy_ssl_conf_command_post }, + #if (T_NGX_SSL_NTLS) { ngx_string("proxy_enable_ntls"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, @@ -897,6 +951,36 @@ static ngx_path_init_t ngx_http_proxy_temp_path = { }; +static ngx_conf_bitmask_t ngx_http_proxy_cookie_flags_masks[] = { + + { ngx_string("secure"), + NGX_HTTP_PROXY_COOKIE_SECURE|NGX_HTTP_PROXY_COOKIE_SECURE_ON }, + + { ngx_string("nosecure"), + NGX_HTTP_PROXY_COOKIE_SECURE|NGX_HTTP_PROXY_COOKIE_SECURE_OFF }, + + { ngx_string("httponly"), + NGX_HTTP_PROXY_COOKIE_HTTPONLY|NGX_HTTP_PROXY_COOKIE_HTTPONLY_ON }, + + { ngx_string("nohttponly"), + NGX_HTTP_PROXY_COOKIE_HTTPONLY|NGX_HTTP_PROXY_COOKIE_HTTPONLY_OFF }, + + { ngx_string("samesite=strict"), + NGX_HTTP_PROXY_COOKIE_SAMESITE|NGX_HTTP_PROXY_COOKIE_SAMESITE_STRICT }, + + { ngx_string("samesite=lax"), + NGX_HTTP_PROXY_COOKIE_SAMESITE|NGX_HTTP_PROXY_COOKIE_SAMESITE_LAX }, + + { ngx_string("samesite=none"), + NGX_HTTP_PROXY_COOKIE_SAMESITE|NGX_HTTP_PROXY_COOKIE_SAMESITE_NONE }, + + { ngx_string("nosamesite"), + NGX_HTTP_PROXY_COOKIE_SAMESITE|NGX_HTTP_PROXY_COOKIE_SAMESITE_OFF }, + + { ngx_null_string, 0 } +}; + + static ngx_int_t ngx_http_proxy_handler(ngx_http_request_t *r) { @@ -958,7 +1042,7 @@ ngx_http_proxy_handler(ngx_http_request_t *r) u->rewrite_redirect = ngx_http_proxy_rewrite_redirect; } - if (plcf->cookie_domains || plcf->cookie_paths) { + if (plcf->cookie_domains || plcf->cookie_paths || plcf->cookie_flags) { u->rewrite_cookie = ngx_http_proxy_rewrite_cookie; } @@ -1154,7 +1238,7 @@ ngx_http_proxy_create_key(ngx_http_request_t *r) loc_len = (r->valid_location && ctx->vars.uri.len) ? plcf->location.len : 0; - if (r->quoted_uri || r->space_in_uri || r->internal) { + if (r->quoted_uri || r->internal) { escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len, r->uri.len - loc_len, NGX_ESCAPE_URI); } else { @@ -1267,7 +1351,7 @@ ngx_http_proxy_create_request(ngx_http_request_t *r) loc_len = (r->valid_location && ctx->vars.uri.len) ? plcf->location.len : 0; - if (r->quoted_uri || r->space_in_uri || r->internal) { + if (r->quoted_uri || r->internal) { escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len, r->uri.len - loc_len, NGX_ESCAPE_URI); } @@ -1987,10 +2071,12 @@ ngx_http_proxy_process_header(ngx_http_request_t *r) return NGX_AGAIN; } - /* there was error while a header line parsing */ + /* rc == NGX_HTTP_PARSE_INVALID_HEADER */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "upstream sent invalid header"); + "upstream sent invalid header: \"%*s\\x%02xd...\"", + r->header_end - r->header_name_start, + r->header_name_start, *r->header_end); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } @@ -2067,6 +2153,25 @@ ngx_http_proxy_copy_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) return NGX_OK; } + if (p->upstream_done) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0, + "http proxy data after close"); + return NGX_OK; + } + + if (p->length == 0) { + + ngx_log_error(NGX_LOG_WARN, p->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); + + r = p->input_ctx; + r->upstream->keepalive = 0; + p->upstream_done = 1; + + return NGX_OK; + } + cl = ngx_chain_get_free_buf(p->pool, &p->free); if (cl == NULL) { return NGX_ERROR; @@ -2094,20 +2199,23 @@ ngx_http_proxy_copy_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) return NGX_OK; } + if (b->last - b->pos > p->length) { + + ngx_log_error(NGX_LOG_WARN, p->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); + + b->last = b->pos + p->length; + p->upstream_done = 1; + + return NGX_OK; + } + p->length -= b->last - b->pos; if (p->length == 0) { r = p->input_ctx; - p->upstream_done = 1; r->upstream->keepalive = !r->upstream->headers_in.connection_close; - - } else if (p->length < 0) { - r = p->input_ctx; - p->upstream_done = 1; - - ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, - "upstream sent more data than specified in " - "\"Content-Length\" header"); } return NGX_OK; @@ -2134,6 +2242,23 @@ ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) return NGX_ERROR; } + if (p->upstream_done) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0, + "http proxy data after close"); + return NGX_OK; + } + + if (p->length == 0) { + + ngx_log_error(NGX_LOG_WARN, p->log, 0, + "upstream sent data after final chunk"); + + r->upstream->keepalive = 0; + p->upstream_done = 1; + + return NGX_OK; + } + b = NULL; prev = &buf->shadow; @@ -2196,9 +2321,15 @@ ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) /* a whole response has been parsed successfully */ - p->upstream_done = 1; + p->length = 0; r->upstream->keepalive = !r->upstream->headers_in.connection_close; + if (buf->pos != buf->last) { + ngx_log_error(NGX_LOG_WARN, p->log, 0, + "upstream sent data after final chunk"); + r->upstream->keepalive = 0; + } + break; } @@ -2213,13 +2344,13 @@ ngx_http_proxy_chunked_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) /* invalid response */ - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + ngx_log_error(NGX_LOG_ERR, p->log, 0, "upstream sent invalid chunked response"); return NGX_ERROR; } - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, p->log, 0, "http proxy chunked state %ui, length %O", ctx->chunked.state, p->length); @@ -2254,6 +2385,14 @@ ngx_http_proxy_non_buffered_copy_filter(void *data, ssize_t bytes) u = r->upstream; + if (u->length == 0) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); + u->keepalive = 0; + return NGX_OK; + } + for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { ll = &cl->next; } @@ -2279,6 +2418,18 @@ ngx_http_proxy_non_buffered_copy_filter(void *data, ssize_t bytes) return NGX_OK; } + if (bytes > u->length) { + + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); + + cl->buf->last = cl->buf->pos + u->length; + u->length = 0; + + return NGX_OK; + } + u->length -= bytes; if (u->length == 0) { @@ -2365,6 +2516,12 @@ ngx_http_proxy_non_buffered_chunked_filter(void *data, ssize_t bytes) u->keepalive = !u->headers_in.connection_close; u->length = 0; + if (buf->pos != buf->last) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent data after final chunk"); + u->keepalive = 0; + } + break; } @@ -2573,7 +2730,7 @@ ngx_http_proxy_rewrite_redirect(ngx_http_request_t *r, ngx_table_elt_t *h, len = h->value.len - prefix; for (i = 0; i < plcf->redirects->nelts; i++) { - rc = pr[i].handler(r, h, prefix, len, &pr[i]); + rc = pr[i].handler(r, &h->value, prefix, len, &pr[i]); if (rc != NGX_DECLINED) { return rc; @@ -2587,27 +2744,43 @@ ngx_http_proxy_rewrite_redirect(ngx_http_request_t *r, ngx_table_elt_t *h, static ngx_int_t ngx_http_proxy_rewrite_cookie(ngx_http_request_t *r, ngx_table_elt_t *h) { - size_t prefix; u_char *p; + size_t len; ngx_int_t rc, rv; + ngx_str_t *key, *value; + ngx_uint_t i; + ngx_array_t attrs; + ngx_keyval_t *attr; ngx_http_proxy_loc_conf_t *plcf; - p = (u_char *) ngx_strchr(h->value.data, ';'); - if (p == NULL) { - return NGX_DECLINED; + if (ngx_array_init(&attrs, r->pool, 2, sizeof(ngx_keyval_t)) != NGX_OK) { + return NGX_ERROR; } - prefix = p + 1 - h->value.data; + if (ngx_http_proxy_parse_cookie(&h->value, &attrs) != NGX_OK) { + return NGX_ERROR; + } + + attr = attrs.elts; + + if (attr[0].value.data == NULL) { + return NGX_DECLINED; + } rv = NGX_DECLINED; plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); - if (plcf->cookie_domains) { - p = ngx_strcasestrn(h->value.data + prefix, "domain=", 7 - 1); + for (i = 1; i < attrs.nelts; i++) { - if (p) { - rc = ngx_http_proxy_rewrite_cookie_value(r, h, p + 7, + key = &attr[i].key; + value = &attr[i].value; + + if (plcf->cookie_domains && key->len == 6 + && ngx_strncasecmp(key->data, (u_char *) "domain", 6) == 0 + && value->data) + { + rc = ngx_http_proxy_rewrite_cookie_value(r, value, plcf->cookie_domains); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -2617,13 +2790,12 @@ ngx_http_proxy_rewrite_cookie(ngx_http_request_t *r, ngx_table_elt_t *h) rv = rc; } } - } - - if (plcf->cookie_paths) { - p = ngx_strcasestrn(h->value.data + prefix, "path=", 5 - 1); - if (p) { - rc = ngx_http_proxy_rewrite_cookie_value(r, h, p + 5, + if (plcf->cookie_paths && key->len == 4 + && ngx_strncasecmp(key->data, (u_char *) "path", 4) == 0 + && value->data) + { + rc = ngx_http_proxy_rewrite_cookie_value(r, value, plcf->cookie_paths); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -2635,30 +2807,153 @@ ngx_http_proxy_rewrite_cookie(ngx_http_request_t *r, ngx_table_elt_t *h) } } - return rv; + if (plcf->cookie_flags) { + rc = ngx_http_proxy_rewrite_cookie_flags(r, &attrs, + plcf->cookie_flags); + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc != NGX_DECLINED) { + rv = rc; + } + + attr = attrs.elts; + } + + if (rv != NGX_OK) { + return rv; + } + + len = 0; + + for (i = 0; i < attrs.nelts; i++) { + + if (attr[i].key.data == NULL) { + continue; + } + + if (i > 0) { + len += 2; + } + + len += attr[i].key.len; + + if (attr[i].value.data) { + len += 1 + attr[i].value.len; + } + } + + p = ngx_pnalloc(r->pool, len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + h->value.data = p; + h->value.len = len; + + for (i = 0; i < attrs.nelts; i++) { + + if (attr[i].key.data == NULL) { + continue; + } + + if (i > 0) { + *p++ = ';'; + *p++ = ' '; + } + + p = ngx_cpymem(p, attr[i].key.data, attr[i].key.len); + + if (attr[i].value.data) { + *p++ = '='; + p = ngx_cpymem(p, attr[i].value.data, attr[i].value.len); + } + } + + *p = '\0'; + + return NGX_OK; } static ngx_int_t -ngx_http_proxy_rewrite_cookie_value(ngx_http_request_t *r, ngx_table_elt_t *h, - u_char *value, ngx_array_t *rewrites) +ngx_http_proxy_parse_cookie(ngx_str_t *value, ngx_array_t *attrs) { - size_t len, prefix; - u_char *p; - ngx_int_t rc; - ngx_uint_t i; - ngx_http_proxy_rewrite_t *pr; + u_char *start, *end, *p, *last; + ngx_str_t name, val; + ngx_keyval_t *attr; + + start = value->data; + end = value->data + value->len; + + for ( ;; ) { + + last = (u_char *) ngx_strchr(start, ';'); + + if (last == NULL) { + last = end; + } + + while (start < last && *start == ' ') { start++; } + + for (p = start; p < last && *p != '='; p++) { /* void */ } + + name.data = start; + name.len = p - start; + + while (name.len && name.data[name.len - 1] == ' ') { + name.len--; + } + + if (p < last) { + + p++; + + while (p < last && *p == ' ') { p++; } + + val.data = p; + val.len = last - val.data; + + while (val.len && val.data[val.len - 1] == ' ') { + val.len--; + } + + } else { + ngx_str_null(&val); + } + + attr = ngx_array_push(attrs); + if (attr == NULL) { + return NGX_ERROR; + } - prefix = value - h->value.data; + attr->key = name; + attr->value = val; - p = (u_char *) ngx_strchr(value, ';'); + if (last == end) { + break; + } - len = p ? (size_t) (p - value) : (h->value.len - prefix); + start = last + 1; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_rewrite_cookie_value(ngx_http_request_t *r, ngx_str_t *value, + ngx_array_t *rewrites) +{ + ngx_int_t rc; + ngx_uint_t i; + ngx_http_proxy_rewrite_t *pr; pr = rewrites->elts; for (i = 0; i < rewrites->nelts; i++) { - rc = pr[i].handler(r, h, prefix, len, &pr[i]); + rc = pr[i].handler(r, value, 0, value->len, &pr[i]); if (rc != NGX_DECLINED) { return rc; @@ -2670,8 +2965,236 @@ ngx_http_proxy_rewrite_cookie_value(ngx_http_request_t *r, ngx_table_elt_t *h, static ngx_int_t -ngx_http_proxy_rewrite_complex_handler(ngx_http_request_t *r, - ngx_table_elt_t *h, size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr) +ngx_http_proxy_rewrite_cookie_flags(ngx_http_request_t *r, ngx_array_t *attrs, + ngx_array_t *flags) +{ + ngx_str_t pattern, value; +#if (NGX_PCRE) + ngx_int_t rc; +#endif + ngx_uint_t i, m, f, nelts; + ngx_keyval_t *attr; + ngx_conf_bitmask_t *mask; + ngx_http_complex_value_t *flags_values; + ngx_http_proxy_cookie_flags_t *pcf; + + attr = attrs->elts; + pcf = flags->elts; + + for (i = 0; i < flags->nelts; i++) { + +#if (NGX_PCRE) + if (pcf[i].regex) { + rc = ngx_http_regex_exec(r, pcf[i].cookie.regex, &attr[0].key); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + break; + } + + /* NGX_DECLINED */ + + continue; + } +#endif + + if (ngx_http_complex_value(r, &pcf[i].cookie.complex, &pattern) + != NGX_OK) + { + return NGX_ERROR; + } + + if (pattern.len == attr[0].key.len + && ngx_strncasecmp(attr[0].key.data, pattern.data, pattern.len) + == 0) + { + break; + } + } + + if (i == flags->nelts) { + return NGX_DECLINED; + } + + nelts = pcf[i].flags_values.nelts; + flags_values = pcf[i].flags_values.elts; + + mask = ngx_http_proxy_cookie_flags_masks; + f = 0; + + for (i = 0; i < nelts; i++) { + + if (ngx_http_complex_value(r, &flags_values[i], &value) != NGX_OK) { + return NGX_ERROR; + } + + if (value.len == 0) { + continue; + } + + for (m = 0; mask[m].name.len != 0; m++) { + + if (mask[m].name.len != value.len + || ngx_strncasecmp(mask[m].name.data, value.data, value.len) + != 0) + { + continue; + } + + f |= mask[m].mask; + + break; + } + + if (mask[m].name.len == 0) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "invalid proxy_cookie_flags flag \"%V\"", &value); + } + } + + if (f == 0) { + return NGX_DECLINED; + } + + return ngx_http_proxy_edit_cookie_flags(r, attrs, f); +} + + +static ngx_int_t +ngx_http_proxy_edit_cookie_flags(ngx_http_request_t *r, ngx_array_t *attrs, + ngx_uint_t flags) +{ + ngx_str_t *key, *value; + ngx_uint_t i; + ngx_keyval_t *attr; + + attr = attrs->elts; + + for (i = 1; i < attrs->nelts; i++) { + key = &attr[i].key; + + if (key->len == 6 + && ngx_strncasecmp(key->data, (u_char *) "secure", 6) == 0) + { + if (flags & NGX_HTTP_PROXY_COOKIE_SECURE_ON) { + flags &= ~NGX_HTTP_PROXY_COOKIE_SECURE_ON; + + } else if (flags & NGX_HTTP_PROXY_COOKIE_SECURE_OFF) { + key->data = NULL; + } + + continue; + } + + if (key->len == 8 + && ngx_strncasecmp(key->data, (u_char *) "httponly", 8) == 0) + { + if (flags & NGX_HTTP_PROXY_COOKIE_HTTPONLY_ON) { + flags &= ~NGX_HTTP_PROXY_COOKIE_HTTPONLY_ON; + + } else if (flags & NGX_HTTP_PROXY_COOKIE_HTTPONLY_OFF) { + key->data = NULL; + } + + continue; + } + + if (key->len == 8 + && ngx_strncasecmp(key->data, (u_char *) "samesite", 8) == 0) + { + value = &attr[i].value; + + if (flags & NGX_HTTP_PROXY_COOKIE_SAMESITE_STRICT) { + flags &= ~NGX_HTTP_PROXY_COOKIE_SAMESITE_STRICT; + + if (value->len != 6 + || ngx_strncasecmp(value->data, (u_char *) "strict", 6) + != 0) + { + ngx_str_set(key, "SameSite"); + ngx_str_set(value, "Strict"); + } + + } else if (flags & NGX_HTTP_PROXY_COOKIE_SAMESITE_LAX) { + flags &= ~NGX_HTTP_PROXY_COOKIE_SAMESITE_LAX; + + if (value->len != 3 + || ngx_strncasecmp(value->data, (u_char *) "lax", 3) != 0) + { + ngx_str_set(key, "SameSite"); + ngx_str_set(value, "Lax"); + } + + } else if (flags & NGX_HTTP_PROXY_COOKIE_SAMESITE_NONE) { + flags &= ~NGX_HTTP_PROXY_COOKIE_SAMESITE_NONE; + + if (value->len != 4 + || ngx_strncasecmp(value->data, (u_char *) "none", 4) != 0) + { + ngx_str_set(key, "SameSite"); + ngx_str_set(value, "None"); + } + + } else if (flags & NGX_HTTP_PROXY_COOKIE_SAMESITE_OFF) { + key->data = NULL; + } + + continue; + } + } + + if (flags & NGX_HTTP_PROXY_COOKIE_SECURE_ON) { + attr = ngx_array_push(attrs); + if (attr == NULL) { + return NGX_ERROR; + } + + ngx_str_set(&attr->key, "Secure"); + ngx_str_null(&attr->value); + } + + if (flags & NGX_HTTP_PROXY_COOKIE_HTTPONLY_ON) { + attr = ngx_array_push(attrs); + if (attr == NULL) { + return NGX_ERROR; + } + + ngx_str_set(&attr->key, "HttpOnly"); + ngx_str_null(&attr->value); + } + + if (flags & (NGX_HTTP_PROXY_COOKIE_SAMESITE_STRICT + |NGX_HTTP_PROXY_COOKIE_SAMESITE_LAX + |NGX_HTTP_PROXY_COOKIE_SAMESITE_NONE)) + { + attr = ngx_array_push(attrs); + if (attr == NULL) { + return NGX_ERROR; + } + + ngx_str_set(&attr->key, "SameSite"); + + if (flags & NGX_HTTP_PROXY_COOKIE_SAMESITE_STRICT) { + ngx_str_set(&attr->value, "Strict"); + + } else if (flags & NGX_HTTP_PROXY_COOKIE_SAMESITE_LAX) { + ngx_str_set(&attr->value, "Lax"); + + } else { + ngx_str_set(&attr->value, "None"); + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_rewrite_complex_handler(ngx_http_request_t *r, ngx_str_t *value, + size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr) { ngx_str_t pattern, replacement; @@ -2680,8 +3203,7 @@ ngx_http_proxy_rewrite_complex_handler(ngx_http_request_t *r, } if (pattern.len > len - || ngx_rstrncmp(h->value.data + prefix, pattern.data, - pattern.len) != 0) + || ngx_rstrncmp(value->data + prefix, pattern.data, pattern.len) != 0) { return NGX_DECLINED; } @@ -2690,20 +3212,20 @@ ngx_http_proxy_rewrite_complex_handler(ngx_http_request_t *r, return NGX_ERROR; } - return ngx_http_proxy_rewrite(r, h, prefix, pattern.len, &replacement); + return ngx_http_proxy_rewrite(r, value, prefix, pattern.len, &replacement); } #if (NGX_PCRE) static ngx_int_t -ngx_http_proxy_rewrite_regex_handler(ngx_http_request_t *r, ngx_table_elt_t *h, +ngx_http_proxy_rewrite_regex_handler(ngx_http_request_t *r, ngx_str_t *value, size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr) { ngx_str_t pattern, replacement; pattern.len = len; - pattern.data = h->value.data + prefix; + pattern.data = value->data + prefix; if (ngx_http_regex_exec(r, pr->pattern.regex, &pattern) != NGX_OK) { return NGX_DECLINED; @@ -2713,20 +3235,15 @@ ngx_http_proxy_rewrite_regex_handler(ngx_http_request_t *r, ngx_table_elt_t *h, return NGX_ERROR; } - if (prefix == 0 && h->value.len == len) { - h->value = replacement; - return NGX_OK; - } - - return ngx_http_proxy_rewrite(r, h, prefix, len, &replacement); + return ngx_http_proxy_rewrite(r, value, prefix, len, &replacement); } #endif static ngx_int_t -ngx_http_proxy_rewrite_domain_handler(ngx_http_request_t *r, - ngx_table_elt_t *h, size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr) +ngx_http_proxy_rewrite_domain_handler(ngx_http_request_t *r, ngx_str_t *value, + size_t prefix, size_t len, ngx_http_proxy_rewrite_t *pr) { u_char *p; ngx_str_t pattern, replacement; @@ -2735,9 +3252,9 @@ ngx_http_proxy_rewrite_domain_handler(ngx_http_request_t *r, return NGX_ERROR; } - p = h->value.data + prefix; + p = value->data + prefix; - if (p[0] == '.') { + if (len && p[0] == '.') { p++; prefix++; len--; @@ -2751,18 +3268,23 @@ ngx_http_proxy_rewrite_domain_handler(ngx_http_request_t *r, return NGX_ERROR; } - return ngx_http_proxy_rewrite(r, h, prefix, len, &replacement); + return ngx_http_proxy_rewrite(r, value, prefix, len, &replacement); } static ngx_int_t -ngx_http_proxy_rewrite(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix, +ngx_http_proxy_rewrite(ngx_http_request_t *r, ngx_str_t *value, size_t prefix, size_t len, ngx_str_t *replacement) { u_char *p, *data; size_t new_len; - new_len = replacement->len + h->value.len - len; + if (len == value->len) { + *value = *replacement; + return NGX_OK; + } + + new_len = replacement->len + value->len - len; if (replacement->len > len) { @@ -2771,23 +3293,22 @@ ngx_http_proxy_rewrite(ngx_http_request_t *r, ngx_table_elt_t *h, size_t prefix, return NGX_ERROR; } - p = ngx_copy(data, h->value.data, prefix); + p = ngx_copy(data, value->data, prefix); p = ngx_copy(p, replacement->data, replacement->len); - ngx_memcpy(p, h->value.data + prefix + len, - h->value.len - len - prefix + 1); + ngx_memcpy(p, value->data + prefix + len, + value->len - len - prefix + 1); - h->value.data = data; + value->data = data; } else { - p = ngx_copy(h->value.data + prefix, replacement->data, - replacement->len); + p = ngx_copy(value->data + prefix, replacement->data, replacement->len); - ngx_memmove(p, h->value.data + prefix + len, - h->value.len - len - prefix + 1); + ngx_memmove(p, value->data + prefix + len, + value->len - len - prefix + 1); } - h->value.len = new_len; + value->len = new_len; return NGX_OK; } @@ -2858,12 +3379,9 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf) * conf->upstream.hide_headers_hash = { NULL, 0 }; * 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; * conf->headers.hash = { NULL, 0 }; @@ -2879,8 +3397,6 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf) * 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.store = NGX_CONF_UNSET; @@ -2932,10 +3448,14 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf) #if (NGX_HTTP_SSL) conf->upstream.ssl_session_reuse = NGX_CONF_UNSET; + conf->upstream.ssl_name = NGX_CONF_UNSET_PTR; conf->upstream.ssl_server_name = NGX_CONF_UNSET; conf->upstream.ssl_verify = NGX_CONF_UNSET; + conf->upstream.ssl_certificate = NGX_CONF_UNSET_PTR; + conf->upstream.ssl_certificate_key = NGX_CONF_UNSET_PTR; + conf->upstream.ssl_passwords = NGX_CONF_UNSET_PTR; conf->ssl_verify_depth = NGX_CONF_UNSET_UINT; - conf->ssl_passwords = NGX_CONF_UNSET_PTR; + conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; #if (T_NGX_SSL_NTLS) conf->upstream.tls_method = NULL; @@ -2946,11 +3466,17 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf) /* "proxy_cyclic_temp_file" is disabled */ conf->upstream.cyclic_temp_file = 0; - conf->redirect = NGX_CONF_UNSET; conf->upstream.change_buffering = 1; + conf->headers_source = NGX_CONF_UNSET_PTR; + + conf->method = NGX_CONF_UNSET_PTR; + + conf->redirect = NGX_CONF_UNSET; + conf->cookie_domains = NGX_CONF_UNSET_PTR; conf->cookie_paths = NGX_CONF_UNSET_PTR; + conf->cookie_flags = NGX_CONF_UNSET_PTR; conf->http_version = NGX_CONF_UNSET_UINT; @@ -3241,10 +3767,6 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) #endif - if (conf->method == NULL) { - conf->method = prev->method; - } - ngx_conf_merge_value(conf->upstream.pass_request_headers, prev->upstream.pass_request_headers, 1); ngx_conf_merge_value(conf->upstream.pass_request_body, @@ -3265,10 +3787,8 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 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_ptr_value(conf->upstream.ssl_name, + prev->upstream.ssl_name, NULL); ngx_conf_merge_value(conf->upstream.ssl_server_name, prev->upstream.ssl_server_name, 0); ngx_conf_merge_value(conf->upstream.ssl_verify, @@ -3279,11 +3799,15 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 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); + ngx_conf_merge_ptr_value(conf->upstream.ssl_certificate, + prev->upstream.ssl_certificate, NULL); + ngx_conf_merge_ptr_value(conf->upstream.ssl_certificate_key, + prev->upstream.ssl_certificate_key, NULL); + ngx_conf_merge_ptr_value(conf->upstream.ssl_passwords, + prev->upstream.ssl_passwords, NULL); + + ngx_conf_merge_ptr_value(conf->ssl_conf_commands, + prev->ssl_conf_commands, NULL); if (conf->ssl && ngx_http_proxy_set_ssl(cf, conf) != NGX_OK) { return NGX_CONF_ERROR; @@ -3305,6 +3829,8 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) #endif #endif + ngx_conf_merge_ptr_value(conf->method, prev->method, NULL); + ngx_conf_merge_value(conf->redirect, prev->redirect, 1); if (conf->redirect) { @@ -3360,6 +3886,8 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_ptr_value(conf->cookie_paths, prev->cookie_paths, NULL); + ngx_conf_merge_ptr_value(conf->cookie_flags, prev->cookie_flags, NULL); + ngx_conf_merge_uint_value(conf->http_version, prev->http_version, NGX_HTTP_VERSION_10); @@ -3430,12 +3958,13 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) } } - if (conf->headers_source == NULL) { + ngx_conf_merge_ptr_value(conf->headers_source, prev->headers_source, NULL); + + if (conf->headers_source == prev->headers_source) { conf->headers = prev->headers; #if (NGX_HTTP_CACHE) conf->headers_cache = prev->headers_cache; #endif - conf->headers_source = prev->headers_source; } rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers, @@ -3774,7 +4303,7 @@ ngx_http_proxy_redirect(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_http_compile_complex_value_t ccv; if (plcf->redirect == 0) { - return NGX_CONF_OK; + return "is duplicate"; } plcf->redirect = 1; @@ -3783,16 +4312,12 @@ ngx_http_proxy_redirect(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (cf->args->nelts == 2) { if (ngx_strcmp(value[1].data, "off") == 0) { - plcf->redirect = 0; - plcf->redirects = NULL; - return NGX_CONF_OK; - } - if (ngx_strcmp(value[1].data, "false") == 0) { - ngx_conf_log_error(NGX_LOG_ERR, cf, 0, - "invalid parameter \"false\", use \"off\" instead"); + if (plcf->redirects) { + return "is duplicate"; + } + plcf->redirect = 0; - plcf->redirects = NULL; return NGX_CONF_OK; } @@ -3816,7 +4341,9 @@ ngx_http_proxy_redirect(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - if (ngx_strcmp(value[1].data, "default") == 0) { + if (cf->args->nelts == 2 + && ngx_strcmp(value[1].data, "default") == 0) + { if (plcf->proxy_lengths) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"proxy_redirect default\" cannot be used " @@ -3919,7 +4446,7 @@ ngx_http_proxy_cookie_domain(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_http_compile_complex_value_t ccv; if (plcf->cookie_domains == NULL) { - return NGX_CONF_OK; + return "is duplicate"; } value = cf->args->elts; @@ -3927,6 +4454,11 @@ ngx_http_proxy_cookie_domain(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (cf->args->nelts == 2) { if (ngx_strcmp(value[1].data, "off") == 0) { + + if (plcf->cookie_domains != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + plcf->cookie_domains = NULL; return NGX_CONF_OK; } @@ -4006,7 +4538,7 @@ ngx_http_proxy_cookie_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_http_compile_complex_value_t ccv; if (plcf->cookie_paths == NULL) { - return NGX_CONF_OK; + return "is duplicate"; } value = cf->args->elts; @@ -4014,6 +4546,11 @@ ngx_http_proxy_cookie_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (cf->args->nelts == 2) { if (ngx_strcmp(value[1].data, "off") == 0) { + + if (plcf->cookie_paths != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + plcf->cookie_paths = NULL; return NGX_CONF_OK; } @@ -4083,6 +4620,126 @@ ngx_http_proxy_cookie_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } +static char * +ngx_http_proxy_cookie_flags(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_proxy_loc_conf_t *plcf = conf; + + ngx_str_t *value; + ngx_uint_t i; + ngx_http_complex_value_t *cv; + ngx_http_proxy_cookie_flags_t *pcf; + ngx_http_compile_complex_value_t ccv; +#if (NGX_PCRE) + ngx_regex_compile_t rc; + u_char errstr[NGX_MAX_CONF_ERRSTR]; +#endif + + if (plcf->cookie_flags == NULL) { + return "is duplicate"; + } + + value = cf->args->elts; + + if (cf->args->nelts == 2) { + + if (ngx_strcmp(value[1].data, "off") == 0) { + + if (plcf->cookie_flags != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + plcf->cookie_flags = NULL; + return NGX_CONF_OK; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[1]); + return NGX_CONF_ERROR; + } + + if (plcf->cookie_flags == NGX_CONF_UNSET_PTR) { + plcf->cookie_flags = ngx_array_create(cf->pool, 1, + sizeof(ngx_http_proxy_cookie_flags_t)); + if (plcf->cookie_flags == NULL) { + return NGX_CONF_ERROR; + } + } + + pcf = ngx_array_push(plcf->cookie_flags); + if (pcf == NULL) { + return NGX_CONF_ERROR; + } + + pcf->regex = 0; + + if (value[1].data[0] == '~') { + value[1].len--; + value[1].data++; + +#if (NGX_PCRE) + ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); + + rc.pattern = value[1]; + rc.err.len = NGX_MAX_CONF_ERRSTR; + rc.err.data = errstr; + rc.options = NGX_REGEX_CASELESS; + + pcf->cookie.regex = ngx_http_regex_compile(cf, &rc); + if (pcf->cookie.regex == NULL) { + return NGX_CONF_ERROR; + } + + pcf->regex = 1; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "using regex \"%V\" requires PCRE library", + &value[1]); + return NGX_CONF_ERROR; +#endif + + } else { + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = &pcf->cookie.complex; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + if (ngx_array_init(&pcf->flags_values, cf->pool, cf->args->nelts - 2, + sizeof(ngx_http_complex_value_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + for (i = 2; i < cf->args->nelts; i++) { + + cv = ngx_array_push(&pcf->flags_values); + if (cv == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[i]; + ccv.complex_value = cv; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + return NGX_CONF_OK; +} + + static ngx_int_t ngx_http_proxy_rewrite_regex(ngx_conf_t *cf, ngx_http_proxy_rewrite_t *pr, ngx_str_t *regex, ngx_uint_t caseless) @@ -4272,15 +4929,15 @@ ngx_http_proxy_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_str_t *value; - if (plcf->ssl_passwords != NGX_CONF_UNSET_PTR) { + if (plcf->upstream.ssl_passwords != NGX_CONF_UNSET_PTR) { return "is duplicate"; } value = cf->args->elts; - plcf->ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]); + plcf->upstream.ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]); - if (plcf->ssl_passwords == NULL) { + if (plcf->upstream.ssl_passwords == NULL) { return NGX_CONF_ERROR; } @@ -4321,6 +4978,17 @@ ngx_http_proxy_lowat_check(ngx_conf_t *cf, void *post, void *data) #if (NGX_HTTP_SSL) +static char * +ngx_http_proxy_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) +{ +#ifndef SSL_CONF_FLAG_FILE + return "is not supported on this platform"; +#else + return NGX_CONF_OK; +#endif +} + + static ngx_int_t ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf) { @@ -4348,26 +5016,48 @@ ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf) cln->handler = ngx_ssl_cleanup_ctx; cln->data = plcf->upstream.ssl; - if (plcf->ssl_certificate.len) { + if (ngx_ssl_ciphers(cf, plcf->upstream.ssl, &plcf->ssl_ciphers, 0) + != NGX_OK) + { + return NGX_ERROR; + } + + if (plcf->upstream.ssl_certificate) { - if (plcf->ssl_certificate_key.len == 0) { + if (plcf->upstream.ssl_certificate_key == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"proxy_ssl_certificate_key\" is defined " - "for certificate \"%V\"", &plcf->ssl_certificate); + "for certificate \"%V\"", + &plcf->upstream.ssl_certificate->value); return NGX_ERROR; } + if (plcf->upstream.ssl_certificate->lengths + || plcf->upstream.ssl_certificate_key->lengths) + { + plcf->upstream.ssl_passwords = + ngx_ssl_preserve_passwords(cf, plcf->upstream.ssl_passwords); + if (plcf->upstream.ssl_passwords == NULL) { + return NGX_ERROR; + } + + } else { #if (T_NGX_SSL_NTLS) - if (ngx_ssl_certificate(cf, plcf->upstream.ssl, &plcf->ssl_certificate, - &plcf->ssl_certificate_key, plcf->ssl_passwords, - SSL_NORMAL_CERT) + if (ngx_ssl_certificate(cf, plcf->upstream.ssl, + &plcf->upstream.ssl_certificate->value, + &plcf->upstream.ssl_certificate_key->value, + plcf->upstream.ssl_passwords, + SSL_NORMAL_CERT) #else - if (ngx_ssl_certificate(cf, plcf->upstream.ssl, &plcf->ssl_certificate, - &plcf->ssl_certificate_key, plcf->ssl_passwords) + if (ngx_ssl_certificate(cf, plcf->upstream.ssl, + &plcf->upstream.ssl_certificate->value, + &plcf->upstream.ssl_certificate_key->value, + plcf->upstream.ssl_passwords) #endif - != NGX_OK) - { - return NGX_ERROR; + != NGX_OK) + { + return NGX_ERROR; + } } } @@ -4410,12 +5100,6 @@ ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf) } #endif - if (ngx_ssl_ciphers(cf, plcf->upstream.ssl, &plcf->ssl_ciphers, 0) - != NGX_OK) - { - return NGX_ERROR; - } - if (plcf->upstream.ssl_verify) { if (plcf->ssl_trusted_certificate.len == 0) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, @@ -4443,6 +5127,12 @@ ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf) return NGX_ERROR; } + if (ngx_ssl_conf_commands(cf, plcf->upstream.ssl, plcf->ssl_conf_commands) + != NGX_OK) + { + return NGX_ERROR; + } + return NGX_OK; } diff --git a/src/http/modules/ngx_http_scgi_module.c b/src/http/modules/ngx_http_scgi_module.c index 6c8f30e3eb..45c5f2e169 100644 --- a/src/http/modules/ngx_http_scgi_module.c +++ b/src/http/modules/ngx_http_scgi_module.c @@ -49,6 +49,7 @@ static ngx_int_t ngx_http_scgi_create_request(ngx_http_request_t *r); static ngx_int_t ngx_http_scgi_reinit_request(ngx_http_request_t *r); static ngx_int_t ngx_http_scgi_process_status_line(ngx_http_request_t *r); static ngx_int_t ngx_http_scgi_process_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_scgi_input_filter_init(void *data); static void ngx_http_scgi_abort_request(ngx_http_request_t *r); static void ngx_http_scgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc); @@ -543,6 +544,10 @@ ngx_http_scgi_handler(ngx_http_request_t *r) u->pipe->input_filter = ngx_event_pipe_copy_input_filter; u->pipe->input_ctx = r; + u->input_filter_init = ngx_http_scgi_input_filter_init; + u->input_filter = ngx_http_upstream_non_buffered_filter; + u->input_filter_ctx = r; + if (!scf->upstream.request_buffering && scf->upstream.pass_request_body && !r->headers_in.chunked) @@ -1144,16 +1149,49 @@ ngx_http_scgi_process_header(ngx_http_request_t *r) return NGX_AGAIN; } - /* there was error while a header line parsing */ + /* rc == NGX_HTTP_PARSE_INVALID_HEADER */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "upstream sent invalid header"); + "upstream sent invalid header: \"%*s\\x%02xd...\"", + r->header_end - r->header_name_start, + r->header_name_start, *r->header_end); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } } +static ngx_int_t +ngx_http_scgi_input_filter_init(void *data) +{ + ngx_http_request_t *r = data; + ngx_http_upstream_t *u; + + u = r->upstream; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http scgi filter init s:%ui l:%O", + u->headers_in.status_n, u->headers_in.content_length_n); + + if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT + || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED) + { + u->pipe->length = 0; + u->length = 0; + + } else if (r->method == NGX_HTTP_HEAD) { + u->pipe->length = -1; + u->length = -1; + + } else { + u->pipe->length = u->headers_in.content_length_n; + u->length = u->headers_in.content_length_n; + } + + return NGX_OK; +} + + static void ngx_http_scgi_abort_request(ngx_http_request_t *r) { @@ -1210,7 +1248,6 @@ ngx_http_scgi_create_loc_conf(ngx_conf_t *cf) conf->upstream.store = NGX_CONF_UNSET; conf->upstream.store_access = NGX_CONF_UNSET_UINT; conf->upstream.next_upstream_tries = NGX_CONF_UNSET_UINT; - conf->upstream.buffering = NGX_CONF_UNSET; conf->upstream.request_buffering = NGX_CONF_UNSET; conf->upstream.ignore_client_abort = NGX_CONF_UNSET; diff --git a/src/http/modules/ngx_http_secure_link_module.c b/src/http/modules/ngx_http_secure_link_module.c index 536e09a726..4d4ce6af11 100644 --- a/src/http/modules/ngx_http_secure_link_module.c +++ b/src/http/modules/ngx_http_secure_link_module.c @@ -302,11 +302,12 @@ ngx_http_secure_link_create_conf(ngx_conf_t *cf) /* * set by ngx_pcalloc(): * - * conf->variable = NULL; - * conf->md5 = NULL; * conf->secret = { 0, NULL }; */ + conf->variable = NGX_CONF_UNSET_PTR; + conf->md5 = NGX_CONF_UNSET_PTR; + return conf; } @@ -318,6 +319,9 @@ ngx_http_secure_link_merge_conf(ngx_conf_t *cf, void *parent, void *child) ngx_http_secure_link_conf_t *conf = child; if (conf->secret.data) { + ngx_conf_init_ptr_value(conf->variable, NULL); + ngx_conf_init_ptr_value(conf->md5, NULL); + if (conf->variable || conf->md5) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"secure_link_secret\" cannot be mixed with " @@ -328,13 +332,8 @@ ngx_http_secure_link_merge_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_OK; } - if (conf->variable == NULL) { - conf->variable = prev->variable; - } - - if (conf->md5 == NULL) { - conf->md5 = prev->md5; - } + ngx_conf_merge_ptr_value(conf->variable, prev->variable, NULL); + ngx_conf_merge_ptr_value(conf->md5, prev->md5, NULL); if (conf->variable == NULL && conf->md5 == NULL) { conf->secret = prev->secret; diff --git a/src/http/modules/ngx_http_slice_filter_module.c b/src/http/modules/ngx_http_slice_filter_module.c index c1edbca2b6..186380a2f3 100644 --- a/src/http/modules/ngx_http_slice_filter_module.c +++ b/src/http/modules/ngx_http_slice_filter_module.c @@ -180,6 +180,11 @@ ngx_http_slice_header_filter(ngx_http_request_t *r) r->headers_out.content_range->hash = 0; r->headers_out.content_range = NULL; + if (r->headers_out.accept_ranges) { + r->headers_out.accept_ranges->hash = 0; + r->headers_out.accept_ranges = NULL; + } + r->allow_ranges = 1; r->subrequest_ranges = 1; r->single_range = 1; diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index bd70848bf9..bcc42b122f 100755 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -20,7 +20,7 @@ typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c, #define NGX_DEFAULT_CIPHERS "HIGH:!aNULL:!MD5" #define NGX_DEFAULT_ECDH_CURVE "auto" -#define NGX_HTTP_NPN_ADVERTISE "\x08http/1.1" +#define NGX_HTTP_ALPN_PROTOS "\x08http/1.1\x08http/1.0\x08http/0.9" #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation @@ -29,11 +29,6 @@ static int ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char *in, unsigned int inlen, void *arg); #endif -#ifdef TLSEXT_TYPE_next_proto_neg -static int ngx_http_ssl_npn_advertised(ngx_ssl_conn_t *ssl_conn, - const unsigned char **out, unsigned int *outlen, void *arg); -#endif - static ngx_int_t ngx_http_ssl_static_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_ssl_variable(ngx_http_request_t *r, @@ -60,6 +55,11 @@ static char *ngx_http_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_ssl_ocsp_cache(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +static char *ngx_http_ssl_conf_command_check(ngx_conf_t *cf, void *post, + void *data); static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf); @@ -84,11 +84,23 @@ static ngx_conf_enum_t ngx_http_ssl_verify[] = { }; +static ngx_conf_enum_t ngx_http_ssl_ocsp[] = { + { ngx_string("off"), 0 }, + { ngx_string("on"), 1 }, + { ngx_string("leaf"), 2 }, + { ngx_null_string, 0 } +}; + + static ngx_conf_deprecated_t ngx_http_ssl_deprecated = { ngx_conf_deprecated, "ssl", "listen ... ssl" }; +static ngx_conf_post_t ngx_http_ssl_conf_command_post = + { ngx_http_ssl_conf_command_check }; + + static ngx_command_t ngx_http_ssl_commands[] = { { ngx_string("ssl"), @@ -279,6 +291,27 @@ static ngx_command_t ngx_http_ssl_commands[] = { offsetof(ngx_http_ssl_srv_conf_t, crl), NULL }, + { ngx_string("ssl_ocsp"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_enum_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, ocsp), + &ngx_http_ssl_ocsp }, + + { ngx_string("ssl_ocsp_responder"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, ocsp_responder), + NULL }, + + { ngx_string("ssl_ocsp_cache"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_ssl_ocsp_cache, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + { ngx_string("ssl_stapling"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, @@ -314,6 +347,20 @@ static ngx_command_t ngx_http_ssl_commands[] = { offsetof(ngx_http_ssl_srv_conf_t, early_data), NULL }, + { ngx_string("ssl_conf_command"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE2, + ngx_conf_set_keyval_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, conf_commands), + &ngx_http_ssl_conf_command_post }, + + { ngx_string("ssl_reject_handshake"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, reject_handshake), + NULL }, + ngx_null_command }; @@ -364,6 +411,9 @@ static ngx_http_variable_t ngx_http_ssl_vars[] = { { ngx_string("ssl_ciphers"), NULL, ngx_http_ssl_variable, (uintptr_t) ngx_ssl_get_ciphers, NGX_HTTP_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_curve"), NULL, ngx_http_ssl_variable, + (uintptr_t) ngx_ssl_get_curve, NGX_HTTP_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_curves"), NULL, ngx_http_ssl_variable, (uintptr_t) ngx_ssl_get_curves, NGX_HTTP_VAR_CHANGEABLE, 0 }, @@ -380,6 +430,9 @@ static ngx_http_variable_t ngx_http_ssl_vars[] = { { ngx_string("ssl_server_name"), NULL, ngx_http_ssl_variable, (uintptr_t) ngx_ssl_get_server_name, NGX_HTTP_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_alpn_protocol"), NULL, ngx_http_ssl_variable, + (uintptr_t) ngx_ssl_get_alpn_protocol, NGX_HTTP_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_client_cert"), NULL, ngx_http_ssl_variable, (uintptr_t) ngx_ssl_get_certificate, NGX_HTTP_VAR_CHANGEABLE, 0 }, @@ -489,22 +542,20 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, #endif ) { - srv = - (unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; - srvlen = sizeof(NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1; - + srv = (unsigned char *) NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS; + srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1; } else #endif { - srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE; - srvlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1; + srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS; + srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1; } if (SSL_select_next_proto((unsigned char **) out, outlen, srv, srvlen, in, inlen) != OPENSSL_NPN_NEGOTIATED) { - return SSL_TLSEXT_ERR_NOACK; + return SSL_TLSEXT_ERR_ALERT_FATAL; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -516,60 +567,6 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, #endif -#ifdef TLSEXT_TYPE_next_proto_neg - -static int -ngx_http_ssl_npn_advertised(ngx_ssl_conn_t *ssl_conn, - const unsigned char **out, unsigned int *outlen, void *arg) -{ -#if (NGX_HTTP_V2 || NGX_DEBUG) - ngx_connection_t *c; - - c = ngx_ssl_get_connection(ssl_conn); - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "SSL NPN advertised"); -#endif - -#if (NGX_HTTP_V2) - { - ngx_http_connection_t *hc; -#if (T_NGX_HTTP2_SRV_ENABLE) - ngx_http_v2_srv_conf_t *h2scf; -#endif - - hc = c->data; - -#if (T_NGX_HTTP2_SRV_ENABLE) - h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); -#endif - - if ( -#if (T_NGX_HTTP2_SRV_ENABLE) - ( -#endif - hc->addr_conf->http2 -#if (T_NGX_HTTP2_SRV_ENABLE) - && h2scf->enable != 0) || h2scf->enable == 1 -#endif - ) - { - *out = - (unsigned char *) NGX_HTTP_V2_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; - *outlen = sizeof(NGX_HTTP_V2_NPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1; - - return SSL_TLSEXT_ERR_OK; - } - } -#endif - - *out = (unsigned char *) NGX_HTTP_NPN_ADVERTISE; - *outlen = sizeof(NGX_HTTP_NPN_ADVERTISE) - 1; - - return SSL_TLSEXT_ERR_OK; -} - -#endif - - static ngx_int_t ngx_http_ssl_static_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) @@ -674,6 +671,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf) * sscf->crl = { 0, NULL }; * sscf->ciphers = { 0, NULL }; * sscf->shm_zone = NULL; + * sscf->ocsp_responder = { 0, NULL }; * sscf->stapling_file = { 0, NULL }; * sscf->stapling_responder = { 0, NULL }; */ @@ -687,16 +685,20 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf) #endif sscf->prefer_server_ciphers = NGX_CONF_UNSET; sscf->early_data = NGX_CONF_UNSET; + sscf->reject_handshake = NGX_CONF_UNSET; sscf->buffer_size = NGX_CONF_UNSET_SIZE; sscf->verify = NGX_CONF_UNSET_UINT; sscf->verify_depth = NGX_CONF_UNSET_UINT; sscf->certificates = NGX_CONF_UNSET_PTR; sscf->certificate_keys = NGX_CONF_UNSET_PTR; sscf->passwords = NGX_CONF_UNSET_PTR; + sscf->conf_commands = NGX_CONF_UNSET_PTR; sscf->builtin_session_cache = NGX_CONF_UNSET; sscf->session_timeout = NGX_CONF_UNSET; sscf->session_tickets = NGX_CONF_UNSET; sscf->session_ticket_keys = NGX_CONF_UNSET_PTR; + sscf->ocsp = NGX_CONF_UNSET_UINT; + sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; sscf->stapling = NGX_CONF_UNSET; sscf->stapling_verify = NGX_CONF_UNSET; @@ -760,6 +762,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) prev->prefer_server_ciphers, 0); ngx_conf_merge_value(conf->early_data, prev->early_data, 0); + ngx_conf_merge_value(conf->reject_handshake, prev->reject_handshake, 0); ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols, (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 @@ -801,6 +804,13 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS); + ngx_conf_merge_ptr_value(conf->conf_commands, prev->conf_commands, NULL); + + ngx_conf_merge_uint_value(conf->ocsp, prev->ocsp, 0); + ngx_conf_merge_str_value(conf->ocsp_responder, prev->ocsp_responder, ""); + ngx_conf_merge_ptr_value(conf->ocsp_cache_zone, + prev->ocsp_cache_zone, NULL); + ngx_conf_merge_value(conf->stapling, prev->stapling, 0); ngx_conf_merge_value(conf->stapling_verify, prev->stapling_verify, 0); ngx_conf_merge_str_value(conf->stapling_file, prev->stapling_file, ""); @@ -811,64 +821,39 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) if (conf->enable) { - if (conf->certificates == NULL -#if (T_NGX_SSL_NTLS) - && conf->enc_certificate.len == 0 - && conf->sign_certificate.len == 0 -#endif - ) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, -#if (T_NGX_SSL_NTLS) - "no \"ssl_certificate\", \"ssl_enc_certificate\" or" - " \"ssl_sign_certificate\" is defined for " -#else - "no \"ssl_certificate\" is defined for " -#endif - "the \"ssl\" directive in %s:%ui", - conf->file, conf->line); - return NGX_CONF_ERROR; - } -#if (T_NGX_SSL_NTLS) - if (conf->certificates != NULL) { -#endif - if (conf->certificate_keys == NULL) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no \"ssl_certificate_key\" is defined for " - "the \"ssl\" directive in %s:%ui", - conf->file, conf->line); - return NGX_CONF_ERROR; - } + if (conf->certificates) { + if (conf->certificate_keys == NULL) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate_key\" is defined for " + "the \"ssl\" directive in %s:%ui", + conf->file, conf->line); + return NGX_CONF_ERROR; + } #if (NGX_HTTP_SSL && NGX_SSL_ASYNC) - conf->ssl.async_enable = conf->async_enable; + conf->ssl.async_enable = conf->async_enable; #endif - if (conf->certificate_keys->nelts < conf->certificates->nelts) { + if (conf->certificate_keys->nelts < conf->certificates->nelts) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate_key\" is defined " + "for certificate \"%V\" and " + "the \"ssl\" directive in %s:%ui", + ((ngx_str_t *) conf->certificates->elts) + + conf->certificates->nelts - 1, + conf->file, conf->line); + return NGX_CONF_ERROR; + } + + } else if (!conf->reject_handshake) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no \"ssl_certificate_key\" is defined " - "for certificate \"%V\" and " + "no \"ssl_certificate\" is defined for " "the \"ssl\" directive in %s:%ui", - ((ngx_str_t *) conf->certificates->elts) - + conf->certificates->nelts - 1, conf->file, conf->line); return NGX_CONF_ERROR; } -#if (T_NGX_SSL_NTLS) - } -#endif - } else { + } else if (conf->certificates) { - if (conf->certificates == NULL -#if (T_NGX_SSL_NTLS) - && conf->enc_certificate.len == 0 - && conf->sign_certificate.len == 0 -#endif - ) { - return NGX_CONF_OK; - } -#if (T_NGX_SSL_NTLS) - if (conf->certificates != NULL) { -#endif if (conf->certificate_keys == NULL || conf->certificate_keys->nelts < conf->certificates->nelts) { @@ -879,9 +864,9 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) + conf->certificates->nelts - 1); return NGX_CONF_ERROR; } -#if (T_NGX_SSL_NTLS) - } -#endif + + } else if (!conf->reject_handshake) { + return NGX_CONF_OK; } #if (T_NGX_SSL_NTLS) @@ -951,10 +936,12 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) SSL_CTX_set_alpn_select_cb(conf->ssl.ctx, ngx_http_ssl_alpn_select, NULL); #endif -#ifdef TLSEXT_TYPE_next_proto_neg - SSL_CTX_set_next_protos_advertised_cb(conf->ssl.ctx, - ngx_http_ssl_npn_advertised, NULL); -#endif + if (ngx_ssl_ciphers(cf, &conf->ssl, &conf->ciphers, + conf->prefer_server_ciphers) + != NGX_OK) + { + return NGX_CONF_ERROR; + } if (ngx_http_ssl_compile_certificates(cf, conf) != NGX_OK) { return NGX_CONF_ERROR; @@ -976,7 +963,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; #endif - } else { + } else if (conf->certificates) { /* configure certificates */ @@ -1008,12 +995,6 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } } #endif - if (ngx_ssl_ciphers(cf, &conf->ssl, &conf->ciphers, - conf->prefer_server_ciphers) - != NGX_OK) - { - return NGX_CONF_ERROR; - } conf->ssl.buffer_size = conf->buffer_size; @@ -1046,6 +1027,23 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } + if (conf->ocsp) { + + if (conf->verify == 3) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "\"ssl_ocsp\" is incompatible with " + "\"ssl_verify_client optional_no_ca\""); + return NGX_CONF_ERROR; + } + + if (ngx_ssl_ocsp(cf, &conf->ssl, &conf->ocsp_responder, conf->ocsp, + conf->ocsp_cache_zone) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + } + if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) { return NGX_CONF_ERROR; } @@ -1101,6 +1099,10 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } + if (ngx_ssl_conf_commands(cf, &conf->ssl, conf->conf_commands) != NGX_OK) { + return NGX_CONF_ERROR; + } + return NGX_CONF_OK; } @@ -1114,10 +1116,10 @@ ngx_http_ssl_compile_certificates(ngx_conf_t *cf, ngx_http_complex_value_t *cv; ngx_http_compile_complex_value_t ccv; -#if (T_NGX_SSL_NTLS) - if (conf->certificates == NULL) + if (conf->certificates == NULL) { return NGX_OK; -#endif + } + cert = conf->certificates->elts; key = conf->certificate_keys->elts; nelts = conf->certificates->nelts; @@ -1378,6 +1380,96 @@ ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } +static char * +ngx_http_ssl_ocsp_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_ssl_srv_conf_t *sscf = conf; + + size_t len; + ngx_int_t n; + ngx_str_t *value, name, size; + ngx_uint_t j; + + if (sscf->ocsp_cache_zone != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + if (ngx_strcmp(value[1].data, "off") == 0) { + sscf->ocsp_cache_zone = NULL; + return NGX_CONF_OK; + } + + if (value[1].len <= sizeof("shared:") - 1 + || ngx_strncmp(value[1].data, "shared:", sizeof("shared:") - 1) != 0) + { + goto invalid; + } + + len = 0; + + for (j = sizeof("shared:") - 1; j < value[1].len; j++) { + if (value[1].data[j] == ':') { + break; + } + + len++; + } + + if (len == 0) { + goto invalid; + } + + name.len = len; + name.data = value[1].data + sizeof("shared:") - 1; + + size.len = value[1].len - j - 1; + size.data = name.data + len + 1; + + n = ngx_parse_size(&size); + + if (n == NGX_ERROR) { + goto invalid; + } + + if (n < (ngx_int_t) (8 * ngx_pagesize)) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "OCSP cache \"%V\" is too small", &value[1]); + + return NGX_CONF_ERROR; + } + + sscf->ocsp_cache_zone = ngx_shared_memory_add(cf, &name, n, + &ngx_http_ssl_module_ctx); + if (sscf->ocsp_cache_zone == NULL) { + return NGX_CONF_ERROR; + } + + sscf->ocsp_cache_zone->init = ngx_ssl_ocsp_cache_init; + + return NGX_CONF_OK; + +invalid: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid OCSP cache \"%V\"", &value[1]); + + return NGX_CONF_ERROR; +} + + +static char * +ngx_http_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) +{ +#ifndef SSL_CONF_FLAG_FILE + return "is not supported on this platform"; +#else + return NGX_CONF_OK; +#endif +} + + static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf) { @@ -1396,17 +1488,28 @@ ngx_http_ssl_init(ngx_conf_t *cf) sscf = cscfp[s]->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; - if (sscf->ssl.ctx == NULL || !sscf->stapling) { + if (sscf->ssl.ctx == NULL) { continue; } clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index]; - if (ngx_ssl_stapling_resolver(cf, &sscf->ssl, clcf->resolver, + if (sscf->stapling) { + if (ngx_ssl_stapling_resolver(cf, &sscf->ssl, clcf->resolver, + clcf->resolver_timeout) + != NGX_OK) + { + return NGX_ERROR; + } + } + + if (sscf->ocsp) { + if (ngx_ssl_ocsp_resolver(cf, &sscf->ssl, clcf->resolver, clcf->resolver_timeout) - != NGX_OK) - { - return NGX_ERROR; + != NGX_OK) + { + return NGX_ERROR; + } } } @@ -1427,12 +1530,38 @@ ngx_http_ssl_init(ngx_conf_t *cf) cscf = addr[a].default_server; sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; - if (sscf->certificates == NULL + if (sscf->certificates) { + continue; + } + + if (!sscf->reject_handshake) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate\" is defined for " + "the \"listen ... ssl\" directive in %s:%ui", + cscf->file_name, cscf->line); + return NGX_ERROR; + } + + /* + * if no certificates are defined in the default server, + * check all non-default server blocks + */ + + cscfp = addr[a].servers.elts; + for (s = 0; s < addr[a].servers.nelts; s++) { + + cscf = cscfp[s]; + sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; + + if (sscf->certificates || sscf->reject_handshake) { + continue; + } + #if (T_NGX_SSL_NTLS) - && sscf->sign_certificate.len == 0 - && sscf->enc_certificate.len == 0 -#endif - ) { + if (sscf->sign_certificate.len > 0 || sscf->enc_certificate.len > 0) { + continue; + } +#endif ngx_log_error(NGX_LOG_EMERG, cf->log, 0, #if (T_NGX_SSL_NTLS) "no \"ssl_certificate\", \"ssl_enc_certificate\" " diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h index bc144f5022..9b5b20b110 100644 --- a/src/http/modules/ngx_http_ssl_module.h +++ b/src/http/modules/ngx_http_ssl_module.h @@ -25,6 +25,7 @@ typedef struct { ngx_flag_t prefer_server_ciphers; ngx_flag_t early_data; + ngx_flag_t reject_handshake; ngx_uint_t protocols; @@ -52,12 +53,17 @@ typedef struct { ngx_str_t ciphers; ngx_array_t *passwords; + ngx_array_t *conf_commands; ngx_shm_zone_t *shm_zone; ngx_flag_t session_tickets; ngx_array_t *session_ticket_keys; + ngx_uint_t ocsp; + ngx_str_t ocsp_responder; + ngx_shm_zone_t *ocsp_cache_zone; + ngx_flag_t stapling; ngx_flag_t stapling_verify; ngx_str_t stapling_file; diff --git a/src/http/modules/ngx_http_static_module.c b/src/http/modules/ngx_http_static_module.c index 282d6ee98a..cf29d5a6da 100644 --- a/src/http/modules/ngx_http_static_module.c +++ b/src/http/modules/ngx_http_static_module.c @@ -50,6 +50,7 @@ ngx_http_static_handler(ngx_http_request_t *r) { u_char *last, *location; size_t root, len; + uintptr_t escape; ngx_str_t path; ngx_int_t rc; ngx_uint_t level; @@ -155,14 +156,18 @@ ngx_http_static_handler(ngx_http_request_t *r) return NGX_HTTP_INTERNAL_SERVER_ERROR; } - len = r->uri.len + 1; + escape = 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, + NGX_ESCAPE_URI); - if (!clcf->alias && r->args.len == 0) { + if (!clcf->alias && r->args.len == 0 && escape == 0) { + len = r->uri.len + 1; location = path.data + root; *last = '/'; } else { + len = r->uri.len + escape + 1; + if (r->args.len) { len += r->args.len + 1; } @@ -173,7 +178,13 @@ ngx_http_static_handler(ngx_http_request_t *r) return NGX_HTTP_INTERNAL_SERVER_ERROR; } - last = ngx_copy(location, r->uri.data, r->uri.len); + if (escape) { + last = (u_char *) ngx_escape_uri(location, r->uri.data, + r->uri.len, NGX_ESCAPE_URI); + + } else { + last = ngx_copy(location, r->uri.data, r->uri.len); + } *last = '/'; diff --git a/src/http/modules/ngx_http_stub_status_module.c b/src/http/modules/ngx_http_stub_status_module.c index 84108c8faf..1739d40a9f 100644 --- a/src/http/modules/ngx_http_stub_status_module.c +++ b/src/http/modules/ngx_http_stub_status_module.c @@ -114,16 +114,6 @@ ngx_http_stub_status_handler(ngx_http_request_t *r) ngx_str_set(&r->headers_out.content_type, "text/plain"); r->headers_out.content_type_lowcase = NULL; - if (r->method == NGX_HTTP_HEAD) { - r->headers_out.status = NGX_HTTP_OK; - - rc = ngx_http_send_header(r); - - if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { - return rc; - } - } - size = sizeof("Active connections: \n") + NGX_ATOMIC_T_LEN #if (T_NGX_HTTP_STUB_STATUS) + sizeof("server accepts handled requests request_time\n") - 1 diff --git a/src/http/modules/ngx_http_upstream_keepalive_module.c b/src/http/modules/ngx_http_upstream_keepalive_module.c index 1560807c66..1a4dfd7766 100644 --- a/src/http/modules/ngx_http_upstream_keepalive_module.c +++ b/src/http/modules/ngx_http_upstream_keepalive_module.c @@ -13,6 +13,7 @@ typedef struct { ngx_uint_t max_cached; ngx_uint_t requests; + ngx_msec_t time; ngx_msec_t timeout; ngx_queue_t cache; @@ -86,6 +87,13 @@ static ngx_command_t ngx_http_upstream_keepalive_commands[] = { 0, NULL }, + { ngx_string("keepalive_time"), + NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_upstream_keepalive_srv_conf_t, time), + NULL }, + { ngx_string("keepalive_timeout"), NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, @@ -149,8 +157,9 @@ ngx_http_upstream_init_keepalive(ngx_conf_t *cf, kcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_keepalive_module); + ngx_conf_init_msec_value(kcf->time, 3600000); ngx_conf_init_msec_value(kcf->timeout, 60000); - ngx_conf_init_uint_value(kcf->requests, 100); + ngx_conf_init_uint_value(kcf->requests, 1000); if (kcf->original_init_upstream(cf, us) != NGX_OK) { return NGX_ERROR; @@ -326,6 +335,10 @@ ngx_http_upstream_free_keepalive_peer(ngx_peer_connection_t *pc, void *data, goto invalid; } + if (ngx_current_msec - c->start_time > kp->conf->time) { + goto invalid; + } + if (!u->keepalive) { goto invalid; } @@ -513,6 +526,7 @@ ngx_http_upstream_keepalive_create_conf(ngx_conf_t *cf) * conf->max_cached = 0; */ + conf->time = NGX_CONF_UNSET_MSEC; conf->timeout = NGX_CONF_UNSET_MSEC; conf->requests = NGX_CONF_UNSET_UINT; diff --git a/src/http/modules/ngx_http_userid_filter_module.c b/src/http/modules/ngx_http_userid_filter_module.c index 31cf402f4b..1e33c5c96c 100644 --- a/src/http/modules/ngx_http_userid_filter_module.c +++ b/src/http/modules/ngx_http_userid_filter_module.c @@ -15,12 +15,21 @@ #define NGX_HTTP_USERID_V1 2 #define NGX_HTTP_USERID_ON 3 +#define NGX_HTTP_USERID_COOKIE_OFF 0x0002 +#define NGX_HTTP_USERID_COOKIE_SECURE 0x0004 +#define NGX_HTTP_USERID_COOKIE_HTTPONLY 0x0008 +#define NGX_HTTP_USERID_COOKIE_SAMESITE 0x0010 +#define NGX_HTTP_USERID_COOKIE_SAMESITE_STRICT 0x0020 +#define NGX_HTTP_USERID_COOKIE_SAMESITE_LAX 0x0040 +#define NGX_HTTP_USERID_COOKIE_SAMESITE_NONE 0x0080 + /* 31 Dec 2037 23:55:55 GMT */ #define NGX_HTTP_USERID_MAX_EXPIRES 2145916555 typedef struct { ngx_uint_t enable; + ngx_uint_t flags; ngx_int_t service; @@ -88,6 +97,20 @@ static ngx_conf_enum_t ngx_http_userid_state[] = { }; +static ngx_conf_bitmask_t ngx_http_userid_flags[] = { + { ngx_string("off"), NGX_HTTP_USERID_COOKIE_OFF }, + { ngx_string("secure"), NGX_HTTP_USERID_COOKIE_SECURE }, + { ngx_string("httponly"), NGX_HTTP_USERID_COOKIE_HTTPONLY }, + { ngx_string("samesite=strict"), + NGX_HTTP_USERID_COOKIE_SAMESITE|NGX_HTTP_USERID_COOKIE_SAMESITE_STRICT }, + { ngx_string("samesite=lax"), + NGX_HTTP_USERID_COOKIE_SAMESITE|NGX_HTTP_USERID_COOKIE_SAMESITE_LAX }, + { ngx_string("samesite=none"), + NGX_HTTP_USERID_COOKIE_SAMESITE|NGX_HTTP_USERID_COOKIE_SAMESITE_NONE }, + { ngx_null_string, 0 } +}; + + static ngx_conf_post_handler_pt ngx_http_userid_domain_p = ngx_http_userid_domain; static ngx_conf_post_handler_pt ngx_http_userid_path_p = ngx_http_userid_path; @@ -138,6 +161,13 @@ static ngx_command_t ngx_http_userid_commands[] = { 0, NULL }, + { ngx_string("userid_flags"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, + ngx_conf_set_bitmask_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_userid_conf_t, flags), + &ngx_http_userid_flags }, + { ngx_string("userid_p3p"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, @@ -383,6 +413,26 @@ ngx_http_userid_set_uid(ngx_http_request_t *r, ngx_http_userid_ctx_t *ctx, len += conf->domain.len; } + if (conf->flags & NGX_HTTP_USERID_COOKIE_SECURE) { + len += sizeof("; secure") - 1; + } + + if (conf->flags & NGX_HTTP_USERID_COOKIE_HTTPONLY) { + len += sizeof("; httponly") - 1; + } + + if (conf->flags & NGX_HTTP_USERID_COOKIE_SAMESITE_STRICT) { + len += sizeof("; samesite=strict") - 1; + } + + if (conf->flags & NGX_HTTP_USERID_COOKIE_SAMESITE_LAX) { + len += sizeof("; samesite=lax") - 1; + } + + if (conf->flags & NGX_HTTP_USERID_COOKIE_SAMESITE_NONE) { + len += sizeof("; samesite=none") - 1; + } + cookie = ngx_pnalloc(r->pool, len); if (cookie == NULL) { return NGX_ERROR; @@ -422,6 +472,26 @@ ngx_http_userid_set_uid(ngx_http_request_t *r, ngx_http_userid_ctx_t *ctx, p = ngx_copy(p, conf->path.data, conf->path.len); + if (conf->flags & NGX_HTTP_USERID_COOKIE_SECURE) { + p = ngx_cpymem(p, "; secure", sizeof("; secure") - 1); + } + + if (conf->flags & NGX_HTTP_USERID_COOKIE_HTTPONLY) { + p = ngx_cpymem(p, "; httponly", sizeof("; httponly") - 1); + } + + if (conf->flags & NGX_HTTP_USERID_COOKIE_SAMESITE_STRICT) { + p = ngx_cpymem(p, "; samesite=strict", sizeof("; samesite=strict") - 1); + } + + if (conf->flags & NGX_HTTP_USERID_COOKIE_SAMESITE_LAX) { + p = ngx_cpymem(p, "; samesite=lax", sizeof("; samesite=lax") - 1); + } + + if (conf->flags & NGX_HTTP_USERID_COOKIE_SAMESITE_NONE) { + p = ngx_cpymem(p, "; samesite=none", sizeof("; samesite=none") - 1); + } + set_cookie = ngx_list_push(&r->headers_out.headers); if (set_cookie == NULL) { return NGX_ERROR; @@ -658,6 +728,7 @@ ngx_http_userid_create_conf(ngx_conf_t *cf) /* * set by ngx_pcalloc(): * + * conf->flags = 0; * conf->name = { 0, NULL }; * conf->domain = { 0, NULL }; * conf->path = { 0, NULL }; @@ -682,6 +753,9 @@ ngx_http_userid_merge_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_uint_value(conf->enable, prev->enable, NGX_HTTP_USERID_OFF); + ngx_conf_merge_bitmask_value(conf->flags, prev->flags, + (NGX_CONF_BITMASK_SET|NGX_HTTP_USERID_COOKIE_OFF)); + ngx_conf_merge_str_value(conf->name, prev->name, "uid"); ngx_conf_merge_str_value(conf->domain, prev->domain, ""); ngx_conf_merge_str_value(conf->path, prev->path, "; path=/"); diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c index 83af0b893d..d427b2d0ab 100644 --- a/src/http/modules/ngx_http_uwsgi_module.c +++ b/src/http/modules/ngx_http_uwsgi_module.c @@ -54,9 +54,7 @@ typedef struct { 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; + ngx_array_t *ssl_conf_commands; #if (T_NGX_SSL_NTLS) ngx_str_t enc_certificate; @@ -74,6 +72,7 @@ static ngx_int_t ngx_http_uwsgi_create_request(ngx_http_request_t *r); static ngx_int_t ngx_http_uwsgi_reinit_request(ngx_http_request_t *r); static ngx_int_t ngx_http_uwsgi_process_status_line(ngx_http_request_t *r); static ngx_int_t ngx_http_uwsgi_process_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_uwsgi_input_filter_init(void *data); static void ngx_http_uwsgi_abort_request(ngx_http_request_t *r); static void ngx_http_uwsgi_finalize_request(ngx_http_request_t *r, ngx_int_t rc); @@ -102,6 +101,8 @@ static char *ngx_http_uwsgi_cache_key(ngx_conf_t *cf, ngx_command_t *cmd, #if (NGX_HTTP_SSL) static char *ngx_http_uwsgi_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_uwsgi_ssl_conf_command_check(ngx_conf_t *cf, void *post, + void *data); static ngx_int_t ngx_http_uwsgi_set_ssl(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *uwcf); #endif @@ -140,6 +141,9 @@ static ngx_conf_bitmask_t ngx_http_uwsgi_ssl_protocols[] = { { ngx_null_string, 0 } }; +static ngx_conf_post_t ngx_http_uwsgi_ssl_conf_command_post = + { ngx_http_uwsgi_ssl_conf_command_check }; + #endif @@ -557,16 +561,16 @@ static ngx_command_t ngx_http_uwsgi_commands[] = { { ngx_string("uwsgi_ssl_certificate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_conf_set_str_slot, + ngx_http_set_complex_value_zero_slot, NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_uwsgi_loc_conf_t, ssl_certificate), + offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ssl_certificate), NULL }, { ngx_string("uwsgi_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_set_complex_value_zero_slot, NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_uwsgi_loc_conf_t, ssl_certificate_key), + offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ssl_certificate_key), NULL }, { ngx_string("uwsgi_ssl_password_file"), @@ -576,6 +580,13 @@ static ngx_command_t ngx_http_uwsgi_commands[] = { 0, NULL }, + { ngx_string("uwsgi_ssl_conf_command"), + 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_uwsgi_loc_conf_t, ssl_conf_commands), + &ngx_http_uwsgi_ssl_conf_command_post }, + #if (T_NGX_SSL_NTLS) { ngx_string("uwsgi_enable_ntls"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, @@ -755,6 +766,10 @@ ngx_http_uwsgi_handler(ngx_http_request_t *r) u->pipe->input_filter = ngx_event_pipe_copy_input_filter; u->pipe->input_ctx = r; + u->input_filter_init = ngx_http_uwsgi_input_filter_init; + u->input_filter = ngx_http_upstream_non_buffered_filter; + u->input_filter_ctx = r; + if (!uwcf->upstream.request_buffering && uwcf->upstream.pass_request_body && !r->headers_in.chunked) @@ -1193,6 +1208,7 @@ ngx_http_uwsgi_create_request(ngx_http_request_t *r) r->upstream->request_bufs = cl; } + b->flush = 1; cl->next = NULL; return NGX_OK; @@ -1397,16 +1413,49 @@ ngx_http_uwsgi_process_header(ngx_http_request_t *r) return NGX_AGAIN; } - /* there was error while a header line parsing */ + /* rc == NGX_HTTP_PARSE_INVALID_HEADER */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "upstream sent invalid header"); + "upstream sent invalid header: \"%*s\\x%02xd...\"", + r->header_end - r->header_name_start, + r->header_name_start, *r->header_end); return NGX_HTTP_UPSTREAM_INVALID_HEADER; } } +static ngx_int_t +ngx_http_uwsgi_input_filter_init(void *data) +{ + ngx_http_request_t *r = data; + ngx_http_upstream_t *u; + + u = r->upstream; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http uwsgi filter init s:%ui l:%O", + u->headers_in.status_n, u->headers_in.content_length_n); + + if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT + || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED) + { + u->pipe->length = 0; + u->length = 0; + + } else if (r->method == NGX_HTTP_HEAD) { + u->pipe->length = -1; + u->length = -1; + + } else { + u->pipe->length = u->headers_in.content_length_n; + u->length = u->headers_in.content_length_n; + } + + return NGX_OK; +} + + static void ngx_http_uwsgi_abort_request(ngx_http_request_t *r) { @@ -1511,10 +1560,14 @@ ngx_http_uwsgi_create_loc_conf(ngx_conf_t *cf) #if (NGX_HTTP_SSL) conf->upstream.ssl_session_reuse = NGX_CONF_UNSET; + conf->upstream.ssl_name = NGX_CONF_UNSET_PTR; 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; + conf->upstream.ssl_certificate = NGX_CONF_UNSET_PTR; + conf->upstream.ssl_certificate_key = NGX_CONF_UNSET_PTR; + conf->upstream.ssl_passwords = NGX_CONF_UNSET_PTR; + conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; #if (T_NGX_SSL_NTLS) conf->upstream.tls_method = NULL; @@ -1830,10 +1883,8 @@ ngx_http_uwsgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 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_ptr_value(conf->upstream.ssl_name, + prev->upstream.ssl_name, NULL); ngx_conf_merge_value(conf->upstream.ssl_server_name, prev->upstream.ssl_server_name, 0); ngx_conf_merge_value(conf->upstream.ssl_verify, @@ -1844,11 +1895,15 @@ ngx_http_uwsgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 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); + ngx_conf_merge_ptr_value(conf->upstream.ssl_certificate, + prev->upstream.ssl_certificate, NULL); + ngx_conf_merge_ptr_value(conf->upstream.ssl_certificate_key, + prev->upstream.ssl_certificate_key, NULL); + ngx_conf_merge_ptr_value(conf->upstream.ssl_passwords, + prev->upstream.ssl_passwords, NULL); + + ngx_conf_merge_ptr_value(conf->ssl_conf_commands, + prev->ssl_conf_commands, NULL); if (conf->ssl && ngx_http_uwsgi_set_ssl(cf, conf) != NGX_OK) { return NGX_CONF_ERROR; @@ -2394,15 +2449,15 @@ ngx_http_uwsgi_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_str_t *value; - if (uwcf->ssl_passwords != NGX_CONF_UNSET_PTR) { + if (uwcf->upstream.ssl_passwords != NGX_CONF_UNSET_PTR) { return "is duplicate"; } value = cf->args->elts; - uwcf->ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]); + uwcf->upstream.ssl_passwords = ngx_ssl_read_password_file(cf, &value[1]); - if (uwcf->ssl_passwords == NULL) { + if (uwcf->upstream.ssl_passwords == NULL) { return NGX_CONF_ERROR; } @@ -2410,6 +2465,17 @@ ngx_http_uwsgi_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } +static char * +ngx_http_uwsgi_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) +{ +#ifndef SSL_CONF_FLAG_FILE + return "is not supported on this platform"; +#else + return NGX_CONF_OK; +#endif +} + + static ngx_int_t ngx_http_uwsgi_set_ssl(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *uwcf) { @@ -2437,26 +2503,48 @@ ngx_http_uwsgi_set_ssl(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *uwcf) cln->handler = ngx_ssl_cleanup_ctx; cln->data = uwcf->upstream.ssl; - if (uwcf->ssl_certificate.len) { + if (ngx_ssl_ciphers(cf, uwcf->upstream.ssl, &uwcf->ssl_ciphers, 0) + != NGX_OK) + { + return NGX_ERROR; + } + + if (uwcf->upstream.ssl_certificate) { - if (uwcf->ssl_certificate_key.len == 0) { + if (uwcf->upstream.ssl_certificate_key == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"uwsgi_ssl_certificate_key\" is defined " - "for certificate \"%V\"", &uwcf->ssl_certificate); + "for certificate \"%V\"", + &uwcf->upstream.ssl_certificate->value); return NGX_ERROR; } + if (uwcf->upstream.ssl_certificate->lengths + || uwcf->upstream.ssl_certificate_key->lengths) + { + uwcf->upstream.ssl_passwords = + ngx_ssl_preserve_passwords(cf, uwcf->upstream.ssl_passwords); + if (uwcf->upstream.ssl_passwords == NULL) { + return NGX_ERROR; + } + + } else { #if (T_NGX_SSL_NTLS) - if (ngx_ssl_certificate(cf, uwcf->upstream.ssl, &uwcf->ssl_certificate, - &uwcf->ssl_certificate_key, uwcf->ssl_passwords, - SSL_NORMAL_CERT) + if (ngx_ssl_certificate(cf, uwcf->upstream.ssl, + &uwcf->upstream.ssl_certificate->value, + &uwcf->upstream.ssl_certificate_key->value, + uwcf->upstream.ssl_passwords, + SSL_NORMAL_CERT) #else - if (ngx_ssl_certificate(cf, uwcf->upstream.ssl, &uwcf->ssl_certificate, - &uwcf->ssl_certificate_key, uwcf->ssl_passwords) + if (ngx_ssl_certificate(cf, uwcf->upstream.ssl, + &uwcf->upstream.ssl_certificate->value, + &uwcf->upstream.ssl_certificate_key->value, + uwcf->upstream.ssl_passwords) #endif - != NGX_OK) - { - return NGX_ERROR; + != NGX_OK) + { + return NGX_ERROR; + } } } @@ -2499,12 +2587,6 @@ ngx_http_uwsgi_set_ssl(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *uwcf) } #endif - if (ngx_ssl_ciphers(cf, uwcf->upstream.ssl, &uwcf->ssl_ciphers, 0) - != NGX_OK) - { - return NGX_ERROR; - } - if (uwcf->upstream.ssl_verify) { if (uwcf->ssl_trusted_certificate.len == 0) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, @@ -2532,6 +2614,12 @@ ngx_http_uwsgi_set_ssl(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *uwcf) return NGX_ERROR; } + if (ngx_ssl_conf_commands(cf, uwcf->upstream.ssl, uwcf->ssl_conf_commands) + != NGX_OK) + { + return NGX_ERROR; + } + return NGX_OK; } diff --git a/src/http/modules/ngx_http_xslt_filter_module.c b/src/http/modules/ngx_http_xslt_filter_module.c index b2f107dc0a..8afd656af4 100644 --- a/src/http/modules/ngx_http_xslt_filter_module.c +++ b/src/http/modules/ngx_http_xslt_filter_module.c @@ -233,6 +233,7 @@ ngx_http_xslt_header_filter(ngx_http_request_t *r) ngx_http_set_ctx(r, ctx, ngx_http_xslt_filter_module); r->main_filter_need_in_memory = 1; + r->allow_ranges = 0; return NGX_OK; } diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index c9299b01a4..2b79154ece 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -37,6 +37,8 @@ static ngx_int_t ngx_http_init_locations(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, ngx_http_core_loc_conf_t *pclcf); static ngx_int_t ngx_http_init_static_location_trees(ngx_conf_t *cf, ngx_http_core_loc_conf_t *pclcf); +static ngx_int_t ngx_http_escape_location_name(ngx_conf_t *cf, + ngx_http_core_loc_conf_t *clcf); static ngx_int_t ngx_http_cmp_locations(const ngx_queue_t *one, const ngx_queue_t *two); static ngx_int_t ngx_http_join_exact_locations(ngx_conf_t *cf, @@ -898,6 +900,41 @@ ngx_http_add_location(ngx_conf_t *cf, ngx_queue_t **locations, ngx_queue_insert_tail(*locations, &lq->queue); + if (ngx_http_escape_location_name(cf, clcf) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_escape_location_name(ngx_conf_t *cf, ngx_http_core_loc_conf_t *clcf) +{ + u_char *p; + size_t len; + uintptr_t escape; + + escape = 2 * ngx_escape_uri(NULL, clcf->name.data, clcf->name.len, + NGX_ESCAPE_URI); + + if (escape) { + len = clcf->name.len + escape; + + p = ngx_pnalloc(cf->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + clcf->escaped_name.len = len; + clcf->escaped_name.data = p; + + ngx_escape_uri(p, clcf->name.data, clcf->name.len, NGX_ESCAPE_URI); + + } else { + clcf->escaped_name = clcf->name; + } + return NGX_OK; } @@ -1317,13 +1354,12 @@ ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, } #if (NGX_HTTP_V2 && NGX_HTTP_SSL \ - && !defined TLSEXT_TYPE_application_layer_protocol_negotiation \ - && !defined TLSEXT_TYPE_next_proto_neg) + && !defined TLSEXT_TYPE_application_layer_protocol_negotiation) if (lsopt->http2 && lsopt->ssl) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "nginx was built with OpenSSL that lacks ALPN " - "and NPN support, HTTP/2 is not enabled for %V", + "support, HTTP/2 is not enabled for %V", &lsopt->addr_text); } @@ -1485,14 +1521,14 @@ ngx_http_server_names(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf, NGX_HASH_WILDCARD_KEY); if (rc == NGX_ERROR) { - return NGX_ERROR; + goto failed; } if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "invalid server name or wildcard \"%V\" on %V", &name[n].name, &addr->opt.addr_text); - return NGX_ERROR; + goto failed; } if (rc == NGX_BUSY) { @@ -1730,7 +1766,6 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) cscf = addr->default_server; ls->pool_size = cscf->connection_pool_size; - ls->post_accept_timeout = cscf->client_header_timeout; clcf = cscf->ctx->loc_conf[ngx_http_core_module.ctx_index]; diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index ce2ef8faee..492696aec6 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -167,6 +167,14 @@ ngx_uint_t ngx_http_degraded(ngx_http_request_t *); #endif +#if (NGX_HTTP_V2) +ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len, + u_char **dst, ngx_uint_t last, ngx_log_t *log); +size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst, + ngx_uint_t lower); +#endif + + extern ngx_module_t ngx_http_module; extern ngx_str_t ngx_http_html_default_types[]; diff --git a/src/http/ngx_http_cache.h b/src/http/ngx_http_cache.h index f9e9664099..bb936c5fe0 100644 --- a/src/http/ngx_http_cache.h +++ b/src/http/ngx_http_cache.h @@ -80,6 +80,7 @@ struct ngx_http_cache_s { ngx_str_t vary; u_char variant[NGX_HTTP_CACHE_KEY_LEN]; + size_t buffer_size; size_t header_start; size_t body_start; off_t length; @@ -116,6 +117,7 @@ struct ngx_http_cache_s { unsigned purged:1; unsigned reading:1; unsigned secondary:1; + unsigned update_variant:1; unsigned background:1; unsigned stale_updating:1; @@ -160,6 +162,7 @@ struct ngx_http_file_cache_s { ngx_path_t *path; + off_t min_free; off_t max_size; size_t bsize; diff --git a/src/http/ngx_http_copy_filter_module.c b/src/http/ngx_http_copy_filter_module.c index c8ad5daeea..bd3028bc76 100644 --- a/src/http/ngx_http_copy_filter_module.c +++ b/src/http/ngx_http_copy_filter_module.c @@ -19,10 +19,6 @@ typedef struct { static void ngx_http_copy_aio_handler(ngx_output_chain_ctx_t *ctx, ngx_file_t *file); static void ngx_http_copy_aio_event_handler(ngx_event_t *ev); -#if (NGX_HAVE_AIO_SENDFILE) -static ssize_t ngx_http_copy_aio_sendfile_preload(ngx_buf_t *file); -static void ngx_http_copy_aio_sendfile_event_handler(ngx_event_t *ev); -#endif #endif #if (NGX_THREADS) static ngx_int_t ngx_http_copy_thread_handler(ngx_thread_task_t *task, @@ -128,9 +124,6 @@ ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in) #if (NGX_HAVE_FILE_AIO) if (ngx_file_aio && clcf->aio == NGX_HTTP_AIO_ON) { ctx->aio_handler = ngx_http_copy_aio_handler; -#if (NGX_HAVE_AIO_SENDFILE) - ctx->aio_preload = ngx_http_copy_aio_sendfile_preload; -#endif } #endif @@ -207,53 +200,6 @@ ngx_http_copy_aio_event_handler(ngx_event_t *ev) ngx_http_run_posted_requests(c); } - -#if (NGX_HAVE_AIO_SENDFILE) - -static ssize_t -ngx_http_copy_aio_sendfile_preload(ngx_buf_t *file) -{ - ssize_t n; - static u_char buf[1]; - ngx_event_aio_t *aio; - ngx_http_request_t *r; - ngx_output_chain_ctx_t *ctx; - - n = ngx_file_aio_read(file->file, buf, 1, file->file_pos, NULL); - - if (n == NGX_AGAIN) { - aio = file->file->aio; - aio->handler = ngx_http_copy_aio_sendfile_event_handler; - - r = aio->data; - r->main->blocked++; - r->aio = 1; - - ctx = ngx_http_get_module_ctx(r, ngx_http_copy_filter_module); - ctx->aio = 1; - } - - return n; -} - - -static void -ngx_http_copy_aio_sendfile_event_handler(ngx_event_t *ev) -{ - ngx_event_aio_t *aio; - ngx_http_request_t *r; - - aio = ev->data; - r = aio->data; - - r->main->blocked--; - r->aio = 0; - ev->complete = 0; - - r->connection->write->handler(r->connection->write); -} - -#endif #endif @@ -263,6 +209,7 @@ static ngx_int_t ngx_http_copy_thread_handler(ngx_thread_task_t *task, ngx_file_t *file) { ngx_str_t name; + ngx_connection_t *c; ngx_thread_pool_t *tp; ngx_http_request_t *r; ngx_output_chain_ctx_t *ctx; @@ -270,6 +217,27 @@ ngx_http_copy_thread_handler(ngx_thread_task_t *task, ngx_file_t *file) r = file->thread_ctx; + if (r->aio) { + /* + * tolerate sendfile() calls if another operation is already + * running; this can happen due to subrequests, multiple calls + * of the next body filter from a filter, or in HTTP/2 due to + * a write event on the main connection + */ + + c = r->connection; + +#if (NGX_HTTP_V2) + if (r->stream) { + c = r->stream->connection->connection; + } +#endif + + if (task == c->sendfile_task) { + return NGX_OK; + } + } + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); tp = clcf->thread_pool; @@ -323,6 +291,20 @@ ngx_http_copy_thread_event_handler(ngx_event_t *ev) r->main->blocked--; r->aio = 0; +#if (NGX_HTTP_V2) + + if (r->stream) { + /* + * for HTTP/2, update write event to make sure processing will + * reach the main connection to handle sendfile() in threads + */ + + c->write->ready = 1; + c->write->active = 0; + } + +#endif + if (r->done) { /* * trigger connection event handler if the subrequest was diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 8099244aeb..37c3ad56e9 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -531,6 +531,13 @@ static ngx_command_t ngx_http_core_commands[] = { offsetof(ngx_http_core_loc_conf_t, limit_rate_after), NULL }, + { ngx_string("keepalive_time"), + 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_core_loc_conf_t, keepalive_time), + NULL }, + { ngx_string("keepalive_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, ngx_http_core_keepalive, @@ -1148,10 +1155,10 @@ ngx_http_core_find_config_phase(ngx_http_request_t *r, ngx_str_set(&r->headers_out.location->key, "Location"); if (r->args.len == 0) { - r->headers_out.location->value = clcf->name; + r->headers_out.location->value = clcf->escaped_name; } else { - len = clcf->name.len + 1 + r->args.len; + len = clcf->escaped_name.len + 1 + r->args.len; p = ngx_pnalloc(r->pool, len); if (p == NULL) { @@ -1163,7 +1170,7 @@ ngx_http_core_find_config_phase(ngx_http_request_t *r, r->headers_out.location->value.len = len; r->headers_out.location->value.data = p; - p = ngx_cpymem(p, clcf->name.data, clcf->name.len); + p = ngx_cpymem(p, clcf->escaped_name.data, clcf->escaped_name.len); *p++ = '?'; ngx_memcpy(p, r->args.data, r->args.len); } @@ -1335,8 +1342,13 @@ ngx_http_core_auth_delay(ngx_http_request_t *r) ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "delaying unauthorized request"); - if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) { - return NGX_HTTP_INTERNAL_SERVER_ERROR; + if (r->connection->read->ready) { + ngx_post_event(r->connection->read, &ngx_posted_events); + + } else { + if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } } r->read_event_handler = ngx_http_test_reading; @@ -1475,6 +1487,11 @@ ngx_http_update_location_config(ngx_http_request_t *r) } else if (r->connection->requests >= clcf->keepalive_requests) { r->keepalive = 0; + } else if (ngx_current_msec - r->connection->start_time + > clcf->keepalive_time) + { + r->keepalive = 0; + } else if (r->headers_in.msie6 && r->method == NGX_HTTP_POST && (clcf->keepalive_disable @@ -1961,7 +1978,7 @@ ngx_http_send_response(ngx_http_request_t *r, ngx_uint_t status, } } - if (r->method == NGX_HTTP_HEAD || (r != r->main && val.len == 0)) { + if (r != r->main && val.len == 0) { return ngx_http_send_header(r); } @@ -3665,6 +3682,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t *cf) /* * set by ngx_pcalloc(): * + * clcf->escaped_name = { 0, NULL }; * clcf->root = { 0, NULL }; * clcf->limit_except = 0; * clcf->post_action = { 0, NULL }; @@ -3672,17 +3690,12 @@ ngx_http_core_create_loc_conf(ngx_conf_t *cf) * clcf->default_type = { 0, NULL }; * clcf->error_log = NULL; * clcf->error_pages = NULL; - * clcf->client_body_buffers = { 0, 0 }; * clcf->client_body_path = NULL; * clcf->regex = NULL; * clcf->exact_match = 0; * clcf->auto_redirect = 0; * clcf->alias = 0; - * clcf->limit_rate = NULL; - * clcf->limit_rate_after = NULL; * clcf->gzip_proxied = 0; - * clcf->server_tag = { 0, NULL }; - * clcf->server_tag_header = { 0, NULL }; * clcf->keepalive_disable = 0; */ @@ -3719,6 +3732,9 @@ ngx_http_core_create_loc_conf(ngx_conf_t *cf) clcf->send_timeout = NGX_CONF_UNSET_MSEC; clcf->send_lowat = NGX_CONF_UNSET_SIZE; clcf->postpone_output = NGX_CONF_UNSET_SIZE; + clcf->limit_rate = NGX_CONF_UNSET_PTR; + clcf->limit_rate_after = NGX_CONF_UNSET_PTR; + clcf->keepalive_time = NGX_CONF_UNSET_MSEC; clcf->keepalive_timeout = NGX_CONF_UNSET_MSEC; clcf->keepalive_header = NGX_CONF_UNSET; clcf->keepalive_requests = NGX_CONF_UNSET_UINT; @@ -3999,7 +4015,7 @@ ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->internal, prev->internal, 0); ngx_conf_merge_value(conf->sendfile, prev->sendfile, 0); ngx_conf_merge_size_value(conf->sendfile_max_chunk, - prev->sendfile_max_chunk, 0); + prev->sendfile_max_chunk, 2 * 1024 * 1024); ngx_conf_merge_size_value(conf->subrequest_output_buffer_size, prev->subrequest_output_buffer_size, (size_t) ngx_pagesize); @@ -4023,20 +4039,18 @@ ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_size_value(conf->postpone_output, prev->postpone_output, 1460); - if (conf->limit_rate == NULL) { - conf->limit_rate = prev->limit_rate; - } - - if (conf->limit_rate_after == NULL) { - conf->limit_rate_after = prev->limit_rate_after; - } + ngx_conf_merge_ptr_value(conf->limit_rate, prev->limit_rate, NULL); + ngx_conf_merge_ptr_value(conf->limit_rate_after, + prev->limit_rate_after, NULL); + ngx_conf_merge_msec_value(conf->keepalive_time, + prev->keepalive_time, 3600000); ngx_conf_merge_msec_value(conf->keepalive_timeout, prev->keepalive_timeout, 75000); ngx_conf_merge_sec_value(conf->keepalive_header, prev->keepalive_header, 0); ngx_conf_merge_uint_value(conf->keepalive_requests, - prev->keepalive_requests, 100); + prev->keepalive_requests, 1000); ngx_conf_merge_uint_value(conf->lingering_close, #if (T_NGX_MODIFY_DEFAULT_VALUE) prev->lingering_close, NGX_HTTP_LINGERING_OFF); @@ -4392,14 +4406,6 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } - if (ngx_strcmp(value[n].data, "spdy") == 0) { - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "invalid parameter \"spdy\": " - "ngx_http_spdy_module was superseded " - "by ngx_http_v2_module"); - continue; - } - if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) { if (ngx_strcmp(&value[n].data[13], "on") == 0) { @@ -4873,19 +4879,6 @@ ngx_http_core_set_aio(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } -#if (NGX_HAVE_AIO_SENDFILE) - - if (ngx_strcmp(value[1].data, "sendfile") == 0) { - clcf->aio = NGX_HTTP_AIO_ON; - - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "the \"sendfile\" parameter of " - "the \"aio\" directive is deprecated"); - return NGX_CONF_OK; - } - -#endif - if (ngx_strncmp(value[1].data, "threads", 7) == 0 && (value[1].len == 7 || value[1].data[7] == '=')) { diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index 318e5b7d11..546ef6b69c 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -309,6 +309,7 @@ typedef struct { struct ngx_http_core_loc_conf_s { ngx_str_t name; /* location name */ + ngx_str_t escaped_name; #if (NGX_PCRE) ngx_http_regex_t *regex; @@ -373,6 +374,7 @@ struct ngx_http_core_loc_conf_s { ngx_msec_t client_body_timeout; /* client_body_timeout */ ngx_msec_t send_timeout; /* send_timeout */ + ngx_msec_t keepalive_time; /* keepalive_time */ ngx_msec_t keepalive_timeout; /* keepalive_timeout */ ngx_msec_t lingering_time; /* lingering_time */ ngx_msec_t lingering_timeout; /* lingering_timeout */ @@ -529,8 +531,8 @@ ngx_int_t ngx_http_gzip_ok(ngx_http_request_t *r); ngx_int_t ngx_http_subrequest(ngx_http_request_t *r, - ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **sr, - ngx_http_post_subrequest_t *psr, ngx_uint_t flags); + ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr, + ngx_http_post_subrequest_t *ps, ngx_uint_t flags); ngx_int_t ngx_http_internal_redirect(ngx_http_request_t *r, ngx_str_t *uri, ngx_str_t *args); ngx_int_t ngx_http_named_location(ngx_http_request_t *r, ngx_str_t *name); diff --git a/src/http/ngx_http_file_cache.c b/src/http/ngx_http_file_cache.c index ecdf11e287..c40093bca6 100644 --- a/src/http/ngx_http_file_cache.c +++ b/src/http/ngx_http_file_cache.c @@ -294,6 +294,8 @@ ngx_http_file_cache_open(ngx_http_request_t *r) cln->data = c; } + c->buffer_size = c->body_start; + rc = ngx_http_file_cache_exists(cache, c); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -852,7 +854,7 @@ ngx_http_file_cache_exists(ngx_http_file_cache_t *cache, ngx_http_cache_t *c) if (fcn->exists || fcn->uses >= c->min_uses) { c->exists = fcn->exists; - if (fcn->body_start) { + if (fcn->body_start && !c->update_variant) { c->body_start = fcn->body_start; } @@ -1230,7 +1232,7 @@ ngx_http_file_cache_reopen(ngx_http_request_t *r, ngx_http_cache_t *c) c->secondary = 1; c->file.name.len = 0; - c->body_start = c->buf->end - c->buf->start; + c->body_start = c->buffer_size; ngx_memcpy(c->key, c->variant, NGX_HTTP_CACHE_KEY_LEN); @@ -1337,6 +1339,7 @@ ngx_http_file_cache_update_variant(ngx_http_request_t *r, ngx_http_cache_t *c) ngx_shmtx_unlock(&cache->shpool->mutex); c->file.name.len = 0; + c->update_variant = 1; ngx_memcpy(c->key, c->main, NGX_HTTP_CACHE_KEY_LEN); @@ -1959,7 +1962,7 @@ ngx_http_file_cache_manager(void *data) { ngx_http_file_cache_t *cache = data; - off_t size; + off_t size, free; time_t wait; ngx_msec_t elapsed, next; ngx_uint_t count, watermark; @@ -1988,7 +1991,19 @@ ngx_http_file_cache_manager(void *data) size, count, (ngx_int_t) watermark); if (size < cache->max_size && count < watermark) { - break; + + if (!cache->min_free) { + break; + } + + free = ngx_fs_available(cache->path->name.data); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "http file cache free: %O", free); + + if (free > cache->min_free) { + break; + } } wait = ngx_http_file_cache_forced_expire(cache); @@ -2304,7 +2319,7 @@ ngx_http_file_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *confp = conf; - off_t max_size; + off_t max_size, min_free; u_char *last, *p; time_t inactive; ssize_t size; @@ -2341,6 +2356,7 @@ ngx_http_file_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) name.len = 0; size = 0; max_size = NGX_MAX_OFF_T_VALUE; + min_free = 0; value = cf->args->elts; @@ -2476,6 +2492,29 @@ ngx_http_file_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) continue; } + if (ngx_strncmp(value[i].data, "min_free=", 9) == 0) { + +#if (NGX_WIN32 || NGX_HAVE_STATFS || NGX_HAVE_STATVFS) + + s.len = value[i].len - 9; + s.data = value[i].data + 9; + + min_free = ngx_parse_offset(&s); + if (min_free < 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid min_free value \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + +#else + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "min_free is not supported " + "on this platform, ignored"); +#endif + + continue; + } + if (ngx_strncmp(value[i].data, "loader_files=", 13) == 0) { loader_files = ngx_atoi(value[i].data + 13, value[i].len - 13); @@ -2607,6 +2646,7 @@ ngx_http_file_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) cache->inactive = inactive; cache->max_size = max_size; + cache->min_free = min_free; caches = (ngx_array_t *) (confp + cmd->offset); diff --git a/src/http/ngx_http_header_filter_module.c b/src/http/ngx_http_header_filter_module.c index c33879a1a9..cfde04959b 100644 --- a/src/http/ngx_http_header_filter_module.c +++ b/src/http/ngx_http_header_filter_module.c @@ -202,6 +202,10 @@ ngx_http_header_filter(ngx_http_request_t *r) } } + if (r->keepalive && (ngx_terminate || ngx_exiting)) { + r->keepalive = 0; + } + len = sizeof("HTTP/1.x ") - 1 + sizeof(CRLF) - 1 /* the end of the header */ + sizeof(CRLF) - 1; diff --git a/src/http/v2/ngx_http_v2_huff_decode.c b/src/http/ngx_http_huff_decode.c similarity index 99% rename from src/http/v2/ngx_http_v2_huff_decode.c rename to src/http/ngx_http_huff_decode.c index 49ca576f78..14b7b78962 100644 --- a/src/http/v2/ngx_http_v2_huff_decode.c +++ b/src/http/ngx_http_huff_decode.c @@ -15,14 +15,14 @@ typedef struct { u_char emit; u_char sym; u_char ending; -} ngx_http_v2_huff_decode_code_t; +} ngx_http_huff_decode_code_t; -static ngx_inline ngx_int_t ngx_http_v2_huff_decode_bits(u_char *state, +static ngx_inline ngx_int_t ngx_http_huff_decode_bits(u_char *state, u_char *ending, ngx_uint_t bits, u_char **dst); -static ngx_http_v2_huff_decode_code_t ngx_http_v2_huff_decode_codes[256][16] = +static ngx_http_huff_decode_code_t ngx_http_huff_decode_codes[256][16] = { /* 0 */ { @@ -2640,7 +2640,7 @@ static ngx_http_v2_huff_decode_code_t ngx_http_v2_huff_decode_codes[256][16] = ngx_int_t -ngx_http_v2_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, +ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, ngx_uint_t last, ngx_log_t *log) { u_char *end, ch, ending; @@ -2653,7 +2653,7 @@ ngx_http_v2_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, while (src != end) { ch = *src++; - if (ngx_http_v2_huff_decode_bits(state, &ending, ch >> 4, dst) + if (ngx_http_huff_decode_bits(state, &ending, ch >> 4, dst) != NGX_OK) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, @@ -2663,7 +2663,7 @@ ngx_http_v2_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, return NGX_ERROR; } - if (ngx_http_v2_huff_decode_bits(state, &ending, ch & 0xf, dst) + if (ngx_http_huff_decode_bits(state, &ending, ch & 0xf, dst) != NGX_OK) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, @@ -2692,12 +2692,12 @@ ngx_http_v2_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, static ngx_inline ngx_int_t -ngx_http_v2_huff_decode_bits(u_char *state, u_char *ending, ngx_uint_t bits, +ngx_http_huff_decode_bits(u_char *state, u_char *ending, ngx_uint_t bits, u_char **dst) { - ngx_http_v2_huff_decode_code_t code; + ngx_http_huff_decode_code_t code; - code = ngx_http_v2_huff_decode_codes[*state][bits]; + code = ngx_http_huff_decode_codes[*state][bits]; if (code.next == *state) { return NGX_ERROR; diff --git a/src/http/v2/ngx_http_v2_huff_encode.c b/src/http/ngx_http_huff_encode.c similarity index 93% rename from src/http/v2/ngx_http_v2_huff_encode.c rename to src/http/ngx_http_huff_encode.c index 3f822cd0b1..c03b153da8 100644 --- a/src/http/v2/ngx_http_v2_huff_encode.c +++ b/src/http/ngx_http_huff_encode.c @@ -14,10 +14,10 @@ typedef struct { uint32_t code; uint32_t len; -} ngx_http_v2_huff_encode_code_t; +} ngx_http_huff_encode_code_t; -static ngx_http_v2_huff_encode_code_t ngx_http_v2_huff_encode_table[256] = +static ngx_http_huff_encode_code_t ngx_http_huff_encode_table[256] = { {0x00001ff8, 13}, {0x007fffd8, 23}, {0x0fffffe2, 28}, {0x0fffffe3, 28}, {0x0fffffe4, 28}, {0x0fffffe5, 28}, {0x0fffffe6, 28}, {0x0fffffe7, 28}, @@ -87,7 +87,7 @@ static ngx_http_v2_huff_encode_code_t ngx_http_v2_huff_encode_table[256] = /* same as above, but embeds lowercase transformation */ -static ngx_http_v2_huff_encode_code_t ngx_http_v2_huff_encode_table_lc[256] = +static ngx_http_huff_encode_code_t ngx_http_huff_encode_table_lc[256] = { {0x00001ff8, 13}, {0x007fffd8, 23}, {0x0fffffe2, 28}, {0x0fffffe3, 28}, {0x0fffffe4, 28}, {0x0fffffe5, 28}, {0x0fffffe6, 28}, {0x0fffffe7, 28}, @@ -161,10 +161,10 @@ static ngx_http_v2_huff_encode_code_t ngx_http_v2_huff_encode_table_lc[256] = #if (NGX_HAVE_LITTLE_ENDIAN) #if (NGX_HAVE_GCC_BSWAP64) -#define ngx_http_v2_huff_encode_buf(dst, buf) \ +#define ngx_http_huff_encode_buf(dst, buf) \ (*(uint64_t *) (dst) = __builtin_bswap64(buf)) #else -#define ngx_http_v2_huff_encode_buf(dst, buf) \ +#define ngx_http_huff_encode_buf(dst, buf) \ ((dst)[0] = (u_char) ((buf) >> 56), \ (dst)[1] = (u_char) ((buf) >> 48), \ (dst)[2] = (u_char) ((buf) >> 40), \ @@ -176,28 +176,28 @@ static ngx_http_v2_huff_encode_code_t ngx_http_v2_huff_encode_table_lc[256] = #endif #else /* !NGX_HAVE_LITTLE_ENDIAN */ -#define ngx_http_v2_huff_encode_buf(dst, buf) \ +#define ngx_http_huff_encode_buf(dst, buf) \ (*(uint64_t *) (dst) = (buf)) #endif #else /* NGX_PTR_SIZE == 4 */ -#define ngx_http_v2_huff_encode_buf(dst, buf) \ +#define ngx_http_huff_encode_buf(dst, buf) \ (*(uint32_t *) (dst) = htonl(buf)) #endif size_t -ngx_http_v2_huff_encode(u_char *src, size_t len, u_char *dst, ngx_uint_t lower) +ngx_http_huff_encode(u_char *src, size_t len, u_char *dst, ngx_uint_t lower) { - u_char *end; - size_t hlen; - ngx_uint_t buf, pending, code; - ngx_http_v2_huff_encode_code_t *table, *next; + u_char *end; + size_t hlen; + ngx_uint_t buf, pending, code; + ngx_http_huff_encode_code_t *table, *next; - table = lower ? ngx_http_v2_huff_encode_table_lc - : ngx_http_v2_huff_encode_table; + table = lower ? ngx_http_huff_encode_table_lc + : ngx_http_huff_encode_table; hlen = 0; buf = 0; pending = 0; @@ -224,7 +224,7 @@ ngx_http_v2_huff_encode(u_char *src, size_t len, u_char *dst, ngx_uint_t lower) buf |= code >> pending; - ngx_http_v2_huff_encode_buf(&dst[hlen], buf); + ngx_http_huff_encode_buf(&dst[hlen], buf); hlen += sizeof(buf); diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index 02a4877ecb..7b209fa828 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -11,7 +11,7 @@ static uint32_t usual[] = { - 0xffffdbfe, /* 1111 1111 1111 1111 1101 1011 1111 1110 */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ 0x7fff37d6, /* 0111 1111 1111 1111 0011 0111 1101 0110 */ @@ -24,7 +24,7 @@ static uint32_t usual[] = { #endif /* ~}| {zyx wvut srqp onml kjih gfed cba` */ - 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0x7fffffff, /* 0111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ @@ -124,10 +124,8 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) sw_host_end, sw_host_ip_literal, sw_port, - sw_host_http_09, sw_after_slash_in_uri, sw_check_uri, - sw_check_uri_http_09, sw_uri, sw_http_09, sw_http_H, @@ -254,12 +252,10 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) r->method = NGX_HTTP_OPTIONS; } -#if (NGX_HTTP_PROXY_CONNECT) if (ngx_str7_cmp(m, 'C', 'O', 'N', 'N', 'E', 'C', 'T', ' ')) { r->method = NGX_HTTP_CONNECT; } -#endif break; @@ -395,7 +391,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) switch (ch) { case ' ': r->connect_port_end = p; - state = sw_host_http_09; + state = sw_http_09; break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; @@ -507,6 +503,12 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) r->uri_start = p; state = sw_after_slash_in_uri; break; + case '?': + r->uri_start = p; + r->args_start = p + 1; + r->empty_path_in_uri = 1; + state = sw_uri; + break; case ' ': /* * use single "/" from request line to preserve pointers, @@ -514,7 +516,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) */ r->uri_start = r->schema_end + 1; r->uri_end = r->schema_end + 2; - state = sw_host_http_09; + state = sw_http_09; break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; @@ -573,6 +575,13 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) r->uri_start = p; state = sw_after_slash_in_uri; break; + case '?': + r->port_end = p; + r->uri_start = p; + r->args_start = p + 1; + r->empty_path_in_uri = 1; + state = sw_uri; + break; case ' ': r->port_end = p; /* @@ -581,35 +590,13 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) */ r->uri_start = r->schema_end + 1; r->uri_end = r->schema_end + 2; - state = sw_host_http_09; - break; - default: - return NGX_HTTP_PARSE_INVALID_REQUEST; - } - break; - - /* space+ after "http://host[:port] " */ - case sw_host_http_09: - switch (ch) { - case ' ': - break; - case CR: - r->http_minor = 9; - state = sw_almost_done; - break; - case LF: - r->http_minor = 9; - goto done; - case 'H': - r->http_protocol.data = p; - state = sw_http_H; + state = sw_http_09; break; default: return NGX_HTTP_PARSE_INVALID_REQUEST; } break; - /* check "/.", "//", "%", and "\" (Win32) in URI */ case sw_after_slash_in_uri: @@ -621,7 +608,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) switch (ch) { case ' ': r->uri_end = p; - state = sw_check_uri_http_09; + state = sw_http_09; break; case CR: r->uri_end = p; @@ -661,9 +648,10 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) case '+': r->plus_in_uri = 1; break; - case '\0': - return NGX_HTTP_PARSE_INVALID_REQUEST; default: + if (ch < 0x20 || ch == 0x7f) { + return NGX_HTTP_PARSE_INVALID_REQUEST; + } state = sw_check_uri; break; } @@ -693,7 +681,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) break; case ' ': r->uri_end = p; - state = sw_check_uri_http_09; + state = sw_http_09; break; case CR: r->uri_end = p; @@ -725,36 +713,14 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) case '+': r->plus_in_uri = 1; break; - case '\0': - return NGX_HTTP_PARSE_INVALID_REQUEST; - } - break; - - /* space+ after URI */ - case sw_check_uri_http_09: - switch (ch) { - case ' ': - break; - case CR: - r->http_minor = 9; - state = sw_almost_done; - break; - case LF: - r->http_minor = 9; - goto done; - case 'H': - r->http_protocol.data = p; - state = sw_http_H; - break; default: - r->space_in_uri = 1; - state = sw_check_uri; - p--; + if (ch < 0x20 || ch == 0x7f) { + return NGX_HTTP_PARSE_INVALID_REQUEST; + } break; } break; - /* URI */ case sw_uri: @@ -779,8 +745,11 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) case '#': r->complex_uri = 1; break; - case '\0': - return NGX_HTTP_PARSE_INVALID_REQUEST; + default: + if (ch < 0x20 || ch == 0x7f) { + return NGX_HTTP_PARSE_INVALID_REQUEST; + } + break; } break; @@ -801,10 +770,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) state = sw_http_H; break; default: - r->space_in_uri = 1; - state = sw_uri; - p--; - break; + return NGX_HTTP_PARSE_INVALID_REQUEST; } break; @@ -1047,7 +1013,8 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, break; } - if (ch == '\0') { + if (ch <= 0x20 || ch == 0x7f || ch == ':') { + r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; } @@ -1115,7 +1082,8 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, break; } - if (ch == '\0') { + if (ch <= 0x20 || ch == 0x7f) { + r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; } @@ -1138,6 +1106,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, r->header_end = p; goto done; case '\0': + r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; default: r->header_start = p; @@ -1161,6 +1130,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, r->header_end = p; goto done; case '\0': + r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; } break; @@ -1176,6 +1146,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, case LF: goto done; case '\0': + r->header_end = p; return NGX_HTTP_PARSE_INVALID_HEADER; default: state = sw_value; @@ -1279,10 +1250,6 @@ ngx_http_parse_uri(ngx_http_request_t *r) } switch (ch) { - case ' ': - r->space_in_uri = 1; - state = sw_check_uri; - break; case '.': r->complex_uri = 1; state = sw_uri; @@ -1313,6 +1280,9 @@ ngx_http_parse_uri(ngx_http_request_t *r) r->plus_in_uri = 1; break; default: + if (ch <= 0x20 || ch == 0x7f) { + return NGX_ERROR; + } state = sw_check_uri; break; } @@ -1340,9 +1310,6 @@ ngx_http_parse_uri(ngx_http_request_t *r) case '.': r->uri_ext = p + 1; break; - case ' ': - r->space_in_uri = 1; - break; #if (NGX_WIN32) case '\\': r->complex_uri = 1; @@ -1364,6 +1331,11 @@ ngx_http_parse_uri(ngx_http_request_t *r) case '+': r->plus_in_uri = 1; break; + default: + if (ch <= 0x20 || ch == 0x7f) { + return NGX_ERROR; + } + break; } break; @@ -1375,12 +1347,14 @@ ngx_http_parse_uri(ngx_http_request_t *r) } switch (ch) { - case ' ': - r->space_in_uri = 1; - break; case '#': r->complex_uri = 1; break; + default: + if (ch <= 0x20 || ch == 0x7f) { + return NGX_ERROR; + } + break; } break; } @@ -1414,6 +1388,10 @@ ngx_http_parse_complex_uri(ngx_http_request_t *r, ngx_uint_t merge_slashes) r->uri_ext = NULL; r->args_start = NULL; + if (r->empty_path_in_uri) { + *u++ = '/'; + } + ch = *p++; while (p <= r->uri_end) { diff --git a/src/http/ngx_http_parse_time.c b/src/http/ngx_http_parse_time.c deleted file mode 100644 index 985af31725..0000000000 --- a/src/http/ngx_http_parse_time.c +++ /dev/null @@ -1,277 +0,0 @@ - -/* - * Copyright (C) Igor Sysoev - * Copyright (C) Nginx, Inc. - */ - - -#include -#include -#include - - -static ngx_uint_t mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - -time_t -ngx_http_parse_time(u_char *value, size_t len) -{ - u_char *p, *end; - ngx_int_t month; - ngx_uint_t day, year, hour, min, sec; - uint64_t time; - enum { - no = 0, - rfc822, /* Tue, 10 Nov 2002 23:50:13 */ - rfc850, /* Tuesday, 10-Dec-02 23:50:13 */ - isoc /* Tue Dec 10 23:50:13 2002 */ - } fmt; - - fmt = 0; - end = value + len; - -#if (NGX_SUPPRESS_WARN) - day = 32; - year = 2038; -#endif - - for (p = value; p < end; p++) { - if (*p == ',') { - break; - } - - if (*p == ' ') { - fmt = isoc; - break; - } - } - - for (p++; p < end; p++) - if (*p != ' ') { - break; - } - - if (end - p < 18) { - return NGX_ERROR; - } - - if (fmt != isoc) { - if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { - return NGX_ERROR; - } - - day = (*p - '0') * 10 + *(p + 1) - '0'; - p += 2; - - if (*p == ' ') { - if (end - p < 18) { - return NGX_ERROR; - } - fmt = rfc822; - - } else if (*p == '-') { - fmt = rfc850; - - } else { - return NGX_ERROR; - } - - p++; - } - - switch (*p) { - - case 'J': - month = *(p + 1) == 'a' ? 0 : *(p + 2) == 'n' ? 5 : 6; - break; - - case 'F': - month = 1; - break; - - case 'M': - month = *(p + 2) == 'r' ? 2 : 4; - break; - - case 'A': - month = *(p + 1) == 'p' ? 3 : 7; - break; - - case 'S': - month = 8; - break; - - case 'O': - month = 9; - break; - - case 'N': - month = 10; - break; - - case 'D': - month = 11; - break; - - default: - return NGX_ERROR; - } - - p += 3; - - if ((fmt == rfc822 && *p != ' ') || (fmt == rfc850 && *p != '-')) { - return NGX_ERROR; - } - - p++; - - if (fmt == rfc822) { - if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9' - || *(p + 2) < '0' || *(p + 2) > '9' - || *(p + 3) < '0' || *(p + 3) > '9') - { - return NGX_ERROR; - } - - year = (*p - '0') * 1000 + (*(p + 1) - '0') * 100 - + (*(p + 2) - '0') * 10 + *(p + 3) - '0'; - p += 4; - - } else if (fmt == rfc850) { - if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { - return NGX_ERROR; - } - - year = (*p - '0') * 10 + *(p + 1) - '0'; - year += (year < 70) ? 2000 : 1900; - p += 2; - } - - if (fmt == isoc) { - if (*p == ' ') { - p++; - } - - if (*p < '0' || *p > '9') { - return NGX_ERROR; - } - - day = *p++ - '0'; - - if (*p != ' ') { - if (*p < '0' || *p > '9') { - return NGX_ERROR; - } - - day = day * 10 + *p++ - '0'; - } - - if (end - p < 14) { - return NGX_ERROR; - } - } - - if (*p++ != ' ') { - return NGX_ERROR; - } - - if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { - return NGX_ERROR; - } - - hour = (*p - '0') * 10 + *(p + 1) - '0'; - p += 2; - - if (*p++ != ':') { - return NGX_ERROR; - } - - if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { - return NGX_ERROR; - } - - min = (*p - '0') * 10 + *(p + 1) - '0'; - p += 2; - - if (*p++ != ':') { - return NGX_ERROR; - } - - if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { - return NGX_ERROR; - } - - sec = (*p - '0') * 10 + *(p + 1) - '0'; - - if (fmt == isoc) { - p += 2; - - if (*p++ != ' ') { - return NGX_ERROR; - } - - if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9' - || *(p + 2) < '0' || *(p + 2) > '9' - || *(p + 3) < '0' || *(p + 3) > '9') - { - return NGX_ERROR; - } - - year = (*p - '0') * 1000 + (*(p + 1) - '0') * 100 - + (*(p + 2) - '0') * 10 + *(p + 3) - '0'; - } - - if (hour > 23 || min > 59 || sec > 59) { - return NGX_ERROR; - } - - if (day == 29 && month == 1) { - if ((year & 3) || ((year % 100 == 0) && (year % 400) != 0)) { - return NGX_ERROR; - } - - } else if (day > mday[month]) { - return NGX_ERROR; - } - - /* - * shift new year to March 1 and start months from 1 (not 0), - * it is needed for Gauss' formula - */ - - if (--month <= 0) { - month += 12; - year -= 1; - } - - /* Gauss' formula for Gregorian days since March 1, 1 BC */ - - time = (uint64_t) ( - /* days in years including leap years since March 1, 1 BC */ - - 365 * year + year / 4 - year / 100 + year / 400 - - /* days before the month */ - - + 367 * month / 12 - 30 - - /* days before the day */ - - + day - 1 - - /* - * 719527 days were between March 1, 1 BC and March 1, 1970, - * 31 and 28 days were in January and February 1970 - */ - - - 719527 + 31 + 28) * 86400 + hour * 3600 + min * 60 + sec; - -#if (NGX_TIME_T_SIZE <= 4) - - if (time > 0x7fffffff) { - return NGX_ERROR; - } - -#endif - - return (time_t) time; -} diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 19c8b4a12b..c67fdb5fd3 100755 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -53,7 +53,7 @@ static void ngx_http_request_finalizer(ngx_http_request_t *r); static void ngx_http_set_keepalive(ngx_http_request_t *r); static void ngx_http_keepalive_handler(ngx_event_t *ev); -static void ngx_http_set_lingering_close(ngx_http_request_t *r); +static void ngx_http_set_lingering_close(ngx_connection_t *c); static void ngx_http_lingering_close_handler(ngx_event_t *ev); static ngx_int_t ngx_http_post_action(ngx_http_request_t *r); static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error); @@ -210,16 +210,17 @@ ngx_http_header_t ngx_http_headers_in[] = { void ngx_http_init_connection(ngx_connection_t *c) { - ngx_uint_t i; - ngx_event_t *rev; - struct sockaddr_in *sin; - ngx_http_port_t *port; - ngx_http_in_addr_t *addr; - ngx_http_log_ctx_t *ctx; - ngx_http_connection_t *hc; + ngx_uint_t i; + ngx_event_t *rev; + struct sockaddr_in *sin; + ngx_http_port_t *port; + ngx_http_in_addr_t *addr; + ngx_http_log_ctx_t *ctx; + ngx_http_connection_t *hc; + ngx_http_core_srv_conf_t *cscf; #if (NGX_HAVE_INET6) - struct sockaddr_in6 *sin6; - ngx_http_in6_addr_t *addr6; + struct sockaddr_in6 *sin6; + ngx_http_in6_addr_t *addr6; #endif #if (NGX_HTTP_V2 && T_NGX_HTTP2_SRV_ENABLE) ngx_http_v2_srv_conf_t *h2scf; @@ -382,7 +383,9 @@ ngx_http_init_connection(ngx_connection_t *c) return; } - ngx_add_timer(rev, c->listening->post_accept_timeout); + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + + ngx_add_timer(rev, cscf->client_header_timeout); ngx_reusable_connection(c, 1); if (ngx_handle_read_event(rev, 0) != NGX_OK) { @@ -452,7 +455,7 @@ ngx_http_wait_request_handler(ngx_event_t *rev) if (n == NGX_AGAIN) { if (!rev->timer_set) { - ngx_add_timer(rev, c->listening->post_accept_timeout); + ngx_add_timer(rev, cscf->client_header_timeout); ngx_reusable_connection(c, 1); } @@ -639,7 +642,7 @@ ngx_http_alloc_request(ngx_connection_t *c) } #if (NGX_HTTP_SSL) - if (c->ssl) { + if (c->ssl && !c->ssl->sendfile) { r->main_filter_need_in_memory = 1; } #endif @@ -706,6 +709,7 @@ ngx_http_ssl_handshake(ngx_event_t *rev) ngx_http_connection_t *hc; ngx_http_ssl_srv_conf_t *sscf; ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; c = rev->data; hc = c->data; @@ -737,7 +741,9 @@ ngx_http_ssl_handshake(ngx_event_t *rev) rev->ready = 0; if (!rev->timer_set) { - ngx_add_timer(rev, c->listening->post_accept_timeout); + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, + ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); ngx_reusable_connection(c, 1); } @@ -819,12 +825,15 @@ ngx_http_ssl_handshake(ngx_event_t *rev) #endif ngx_reusable_connection(c, 0); + rc = ngx_ssl_handshake(c); if (rc == NGX_AGAIN) { if (!rev->timer_set) { - ngx_add_timer(rev, c->listening->post_accept_timeout); + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, + ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); } c->ssl->handler = ngx_http_ssl_handshake_handler; @@ -867,8 +876,7 @@ ngx_http_ssl_handshake_handler(ngx_connection_t *c) c->ssl->no_wait_shutdown = 1; #if (NGX_HTTP_V2 \ - && (defined TLSEXT_TYPE_application_layer_protocol_negotiation \ - || defined TLSEXT_TYPE_next_proto_neg)) + && defined TLSEXT_TYPE_application_layer_protocol_negotiation) { unsigned int len; const unsigned char *data; @@ -956,10 +964,14 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) return SSL_TLSEXT_ERR_ALERT_FATAL; } + hc = c->data; + servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); if (servername == NULL) { - return SSL_TLSEXT_ERR_OK; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "SSL server name: null"); + goto done; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -968,7 +980,7 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) host.len = ngx_strlen(servername); if (host.len == 0) { - return SSL_TLSEXT_ERR_OK; + goto done; } host.data = (u_char *) servername; @@ -976,32 +988,27 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) rc = ngx_http_validate_host(&host, c->pool, 1); if (rc == NGX_ERROR) { - *ad = SSL_AD_INTERNAL_ERROR; - return SSL_TLSEXT_ERR_ALERT_FATAL; + goto error; } if (rc == NGX_DECLINED) { - return SSL_TLSEXT_ERR_OK; + goto done; } - hc = c->data; - rc = ngx_http_find_virtual_server(c, hc->addr_conf->virtual_names, &host, NULL, &cscf); if (rc == NGX_ERROR) { - *ad = SSL_AD_INTERNAL_ERROR; - return SSL_TLSEXT_ERR_ALERT_FATAL; + goto error; } if (rc == NGX_DECLINED) { - return SSL_TLSEXT_ERR_OK; + goto done; } hc->ssl_servername = ngx_palloc(c->pool, sizeof(ngx_str_t)); if (hc->ssl_servername == NULL) { - *ad = SSL_AD_INTERNAL_ERROR; - return SSL_TLSEXT_ERR_ALERT_FATAL; + goto error; } *hc->ssl_servername = host; @@ -1017,7 +1024,9 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) c->ssl->buffer_size = sscf->buffer_size; if (sscf->ssl.ctx) { - SSL_set_SSL_CTX(ssl_conn, sscf->ssl.ctx); + if (SSL_set_SSL_CTX(ssl_conn, sscf->ssl.ctx) == NULL) { + goto error; + } /* * SSL_set_SSL_CTX() only changes certs as of 1.0.0d @@ -1042,7 +1051,22 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) #endif } +done: + + sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); + + if (sscf->reject_handshake) { + c->ssl->handshake_rejected = 1; + *ad = SSL_AD_UNRECOGNIZED_NAME; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + return SSL_TLSEXT_ERR_OK; + +error: + + *ad = SSL_AD_INTERNAL_ERROR; + return SSL_TLSEXT_ERR_ALERT_FATAL; } #endif @@ -1104,12 +1128,14 @@ ngx_http_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg) } ngx_http_free_request(r, 0); + c->log->action = "SSL handshaking"; c->destroyed = 0; return 1; failed: ngx_http_free_request(r, 0); + c->log->action = "SSL handshaking"; c->destroyed = 0; return 0; } @@ -1340,9 +1366,13 @@ ngx_http_process_request_uri(ngx_http_request_t *r) r->uri.len = r->uri_end - r->uri_start; } - if (r->complex_uri || r->quoted_uri) { + if (r->complex_uri || r->quoted_uri || r->empty_path_in_uri) { + + if (r->empty_path_in_uri) { + r->uri.len++; + } - r->uri.data = ngx_pnalloc(r->pool, r->uri.len + 1); + r->uri.data = ngx_pnalloc(r->pool, r->uri.len); if (r->uri.data == NULL) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_ERROR; @@ -1376,7 +1406,7 @@ ngx_http_process_request_uri(ngx_http_request_t *r) r->unparsed_uri.len = r->uri_end - r->uri_start; r->unparsed_uri.data = r->uri_start; - r->valid_unparsed_uri = r->space_in_uri ? 0 : 1; + r->valid_unparsed_uri = r->empty_path_in_uri ? 0 : 1; if (r->uri_ext) { if (r->args_start) { @@ -1634,7 +1664,9 @@ ngx_http_process_request_headers(ngx_event_t *rev) /* rc == NGX_HTTP_PARSE_INVALID_HEADER */ ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client sent invalid header line"); + "client sent invalid header line: \"%*s\\x%02xd...\"", + r->header_end - r->header_name_start, + r->header_name_start, *r->header_end); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); break; @@ -1801,6 +1833,12 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http large header copy: %uz", r->header_in->pos - old); + if (r->header_in->pos - old > b->end - b->start) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "too large header to copy"); + return NGX_ERROR; + } + new = b->start; ngx_memcpy(new, old, r->header_in->pos - old); @@ -2111,20 +2149,28 @@ ngx_http_process_request_header(ngx_http_request_t *r) } } - if (r->method == NGX_HTTP_TRACE) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent TRACE method"); - ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); - return NGX_ERROR; - } - if (r->headers_in.transfer_encoding) { + if (r->http_version < NGX_HTTP_VERSION_11) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent HTTP/1.0 request with " + "\"Transfer-Encoding\" header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + if (r->headers_in.transfer_encoding->value.len == 7 && ngx_strncasecmp(r->headers_in.transfer_encoding->value.data, (u_char *) "chunked", 7) == 0) { - r->headers_in.content_length = NULL; - r->headers_in.content_length_n = -1; + if (r->headers_in.content_length) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent \"Content-Length\" and " + "\"Transfer-Encoding\" headers " + "at the same time"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + r->headers_in.chunked = 1; } else { @@ -2144,6 +2190,22 @@ ngx_http_process_request_header(ngx_http_request_t *r) } } +#if !(NGX_HTTP_PROXY_CONNECT) + if (r->method == NGX_HTTP_CONNECT) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent CONNECT method"); + ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); + return NGX_ERROR; + } +#endif + + if (r->method == NGX_HTTP_TRACE) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent TRACE method"); + ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); + return NGX_ERROR; + } + return NGX_OK; } @@ -2160,6 +2222,7 @@ ngx_http_process_request(ngx_http_request_t *r) if (r->http_connection->ssl) { long rc; X509 *cert; + const char *s; ngx_http_ssl_srv_conf_t *sscf; if (c->ssl == NULL) { @@ -2204,6 +2267,17 @@ ngx_http_process_request(ngx_http_request_t *r) X509_free(cert); } + + if (ngx_ssl_ocsp_get_status(c, &s) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client SSL certificate verify error: %s", s); + + ngx_ssl_remove_cached_session(c->ssl->session_ctx, + (SSL_get0_session(c->ssl->connection))); + + ngx_http_finalize_request(r, NGX_HTTPS_CERT_ERROR); + return; + } } } @@ -2279,15 +2353,16 @@ ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) } break; - case '\0': - return NGX_DECLINED; - default: if (ngx_path_separator(ch)) { return NGX_DECLINED; } + if (ch <= 0x20 || ch == 0x7f) { + return NGX_DECLINED; + } + if (ch >= 'A' && ch <= 'Z') { alloc = 1; } @@ -2764,11 +2839,6 @@ ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc) ngx_del_timer(c->write); } - if (c->read->eof) { - ngx_http_close_request(r, 0); - return; - } - ngx_http_finalize_connection(r); } @@ -2867,6 +2937,11 @@ ngx_http_finalize_connection(ngx_http_request_t *r) r = r->main; + if (r->connection->read->eof) { + ngx_http_close_request(r, 0); + return; + } + if (r->reading_body) { r->keepalive = 0; r->lingering_close = 1; @@ -2887,7 +2962,7 @@ ngx_http_finalize_connection(ngx_http_request_t *r) || r->header_in->pos < r->header_in->last || r->connection->read->ready))) { - ngx_http_set_lingering_close(r); + ngx_http_set_lingering_close(r->connection); return; } @@ -3141,6 +3216,12 @@ ngx_http_test_reading(ngx_http_request_t *r) rev->error = 1; } +#if (NGX_HTTP_SSL) + if (c->ssl) { + c->ssl->no_send_shutdown = 1; + } +#endif + ngx_log_error(NGX_LOG_INFO, c->log, err, "client prematurely closed connection"); @@ -3166,13 +3247,6 @@ ngx_http_set_keepalive(ngx_http_request_t *r) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "set http keepalive handler"); - if (r->discard_body) { - r->write_event_handler = ngx_http_request_empty_handler; - r->lingering_time = ngx_time() + (time_t) (clcf->lingering_time / 1000); - ngx_add_timer(rev, clcf->lingering_timeout); - return; - } - c->log->action = "closing request"; hc = r->http_connection; @@ -3541,22 +3615,43 @@ ngx_http_keepalive_handler(ngx_event_t *rev) static void -ngx_http_set_lingering_close(ngx_http_request_t *r) +ngx_http_set_lingering_close(ngx_connection_t *c) { ngx_event_t *rev, *wev; - ngx_connection_t *c; + ngx_http_request_t *r; ngx_http_core_loc_conf_t *clcf; - c = r->connection; + r = c->data; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + if (r->lingering_time == 0) { + r->lingering_time = ngx_time() + (time_t) (clcf->lingering_time / 1000); + } + +#if (NGX_HTTP_SSL) + if (c->ssl) { + ngx_int_t rc; + + c->ssl->shutdown_without_free = 1; + + rc = ngx_ssl_shutdown(c); + + if (rc == NGX_ERROR) { + ngx_http_close_request(r, 0); + return; + } + + if (rc == NGX_AGAIN) { + c->ssl->handler = ngx_http_set_lingering_close; + return; + } + } +#endif + rev = c->read; rev->handler = ngx_http_lingering_close_handler; - r->lingering_time = ngx_time() + (time_t) (clcf->lingering_time / 1000); - ngx_add_timer(rev, clcf->lingering_timeout); - if (ngx_handle_read_event(rev, 0) != NGX_OK) { ngx_http_close_request(r, 0); return; @@ -3587,6 +3682,11 @@ ngx_http_set_lingering_close(ngx_http_request_t *r) return; } + c->close = 0; + ngx_reusable_connection(c, 1); + + ngx_add_timer(rev, clcf->lingering_timeout); + if (rev->ready) { ngx_http_lingering_close_handler(rev); } @@ -3609,7 +3709,7 @@ ngx_http_lingering_close_handler(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http lingering close handler"); - if (rev->timedout) { + if (rev->timedout || c->close) { ngx_http_close_request(r, 0); return; } diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 036a72b464..3f9b260219 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -25,26 +25,23 @@ #define NGX_HTTP_VERSION_11 1001 #define NGX_HTTP_VERSION_20 2000 -#define NGX_HTTP_UNKNOWN 0x0001 -#define NGX_HTTP_GET 0x0002 -#define NGX_HTTP_HEAD 0x0004 -#define NGX_HTTP_POST 0x0008 -#define NGX_HTTP_PUT 0x0010 -#define NGX_HTTP_DELETE 0x0020 -#define NGX_HTTP_MKCOL 0x0040 -#define NGX_HTTP_COPY 0x0080 -#define NGX_HTTP_MOVE 0x0100 -#define NGX_HTTP_OPTIONS 0x0200 -#define NGX_HTTP_PROPFIND 0x0400 -#define NGX_HTTP_PROPPATCH 0x0800 -#define NGX_HTTP_LOCK 0x1000 -#define NGX_HTTP_UNLOCK 0x2000 -#define NGX_HTTP_PATCH 0x4000 -#define NGX_HTTP_TRACE 0x8000 - -#if (NGX_HTTP_PROXY_CONNECT) -#define NGX_HTTP_CONNECT 0x10000 -#endif +#define NGX_HTTP_UNKNOWN 0x00000001 +#define NGX_HTTP_GET 0x00000002 +#define NGX_HTTP_HEAD 0x00000004 +#define NGX_HTTP_POST 0x00000008 +#define NGX_HTTP_PUT 0x00000010 +#define NGX_HTTP_DELETE 0x00000020 +#define NGX_HTTP_MKCOL 0x00000040 +#define NGX_HTTP_COPY 0x00000080 +#define NGX_HTTP_MOVE 0x00000100 +#define NGX_HTTP_OPTIONS 0x00000200 +#define NGX_HTTP_PROPFIND 0x00000400 +#define NGX_HTTP_PROPPATCH 0x00000800 +#define NGX_HTTP_LOCK 0x00001000 +#define NGX_HTTP_UNLOCK 0x00002000 +#define NGX_HTTP_PATCH 0x00004000 +#define NGX_HTTP_TRACE 0x00008000 +#define NGX_HTTP_CONNECT 0x00010000 #define NGX_HTTP_CONNECTION_CLOSE 1 #define NGX_HTTP_CONNECTION_KEEP_ALIVE 2 @@ -305,6 +302,9 @@ typedef struct { ngx_chain_t *busy; ngx_http_chunked_t *chunked; ngx_http_client_body_handler_pt post_handler; + unsigned filter_need_buffering:1; + unsigned last_sent:1; + unsigned last_saved:1; } ngx_http_request_body_t; @@ -486,8 +486,8 @@ struct ngx_http_request_s { /* URI with "+" */ unsigned plus_in_uri:1; - /* URI with " " */ - unsigned space_in_uri:1; + /* URI with empty path */ + unsigned empty_path_in_uri:1; unsigned invalid_header:1; @@ -574,11 +574,8 @@ struct ngx_http_request_s { unsigned subrequest_ranges:1; unsigned single_range:1; unsigned disable_not_modified:1; - -#if (NGX_STAT_STUB) unsigned stat_reading:1; unsigned stat_writing:1; -#endif unsigned stat_processing:1; unsigned background:1; diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c index 49f7101b3d..108bb892dc 100644 --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -12,6 +12,8 @@ static void ngx_http_read_client_request_body_handler(ngx_http_request_t *r); static ngx_int_t ngx_http_do_read_client_request_body(ngx_http_request_t *r); +static ngx_int_t ngx_http_copy_pipelined_header(ngx_http_request_t *r, + ngx_buf_t *buf); static ngx_int_t ngx_http_write_request_body(ngx_http_request_t *r); static ngx_int_t ngx_http_read_discarded_request_body(ngx_http_request_t *r); static ngx_int_t ngx_http_discard_request_body_filter(ngx_http_request_t *r, @@ -60,11 +62,16 @@ ngx_http_read_client_request_body(ngx_http_request_t *r, /* * set by ngx_pcalloc(): * + * rb->temp_file = NULL; * rb->bufs = NULL; * rb->buf = NULL; * rb->free = NULL; * rb->busy = NULL; * rb->chunked = NULL; + * rb->received = 0; + * rb->filter_need_buffering = 0; + * rb->last_sent = 0; + * rb->last_saved = 0; */ rb->rest = -1; @@ -135,13 +142,14 @@ ngx_http_read_client_request_body(ngx_http_request_t *r, } else { /* set rb->rest */ - if (ngx_http_request_body_filter(r, NULL) != NGX_OK) { - rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + rc = ngx_http_request_body_filter(r, NULL); + + if (rc != NGX_OK) { goto done; } } - if (rb->rest == 0) { + if (rb->rest == 0 && rb->last_saved) { /* the whole request body was pre-read */ r->request_body_no_buffering = 0; post_handler(r); @@ -169,6 +177,10 @@ ngx_http_read_client_request_body(ngx_http_request_t *r, size += preread; } + if (size == 0) { + size++; + } + } else { size = clcf->client_body_buffer_size; } @@ -267,6 +279,7 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r) size_t size; ssize_t n; ngx_int_t rc; + ngx_uint_t flush; ngx_chain_t out; ngx_connection_t *c; ngx_http_request_body_t *rb; @@ -274,36 +287,25 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r) c = r->connection; rb = r->request_body; + flush = 1; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http read client request body"); for ( ;; ) { for ( ;; ) { - if (rb->buf->last == rb->buf->end) { - - if (rb->buf->pos != rb->buf->last) { - - /* pass buffer to request body filter chain */ - - out.buf = rb->buf; - out.next = NULL; - - rc = ngx_http_request_body_filter(r, &out); - - if (rc != NGX_OK) { - return rc; - } + if (rb->rest == 0) { + break; + } - } else { + if (rb->buf->last == rb->buf->end) { - /* update chains */ + /* update chains */ - rc = ngx_http_request_body_filter(r, NULL); + rc = ngx_http_request_body_filter(r, NULL); - if (rc != NGX_OK) { - return rc; - } + if (rc != NGX_OK) { + return rc; } if (rb->busy != NULL) { @@ -319,9 +321,25 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r) return NGX_AGAIN; } + if (rb->filter_need_buffering) { + clcf = ngx_http_get_module_loc_conf(r, + ngx_http_core_module); + ngx_add_timer(c->read, clcf->client_body_timeout); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "busy buffers after request body flush"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; } + flush = 0; rb->buf->pos = rb->buf->start; rb->buf->last = rb->buf->start; } @@ -333,6 +351,10 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r) size = (size_t) rest; } + if (size == 0) { + break; + } + n = c->recv(c, rb->buf->last, size); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -358,17 +380,16 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r) c->received += n; #endif - if (n == rest) { - /* pass buffer to request body filter chain */ + /* pass buffer to request body filter chain */ - out.buf = rb->buf; - out.next = NULL; + flush = 0; + out.buf = rb->buf; + out.next = NULL; - rc = ngx_http_request_body_filter(r, &out); + rc = ngx_http_request_body_filter(r, &out); - if (rc != NGX_OK) { - return rc; - } + if (rc != NGX_OK) { + return rc; } if (rb->rest == 0) { @@ -383,26 +404,19 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http client request body rest %O", rb->rest); - if (rb->rest == 0) { - break; - } - - if (!c->read->ready) { + if (flush) { + rc = ngx_http_request_body_filter(r, NULL); - if (r->request_body_no_buffering - && rb->buf->pos != rb->buf->last) - { - /* pass buffer to request body filter chain */ - - out.buf = rb->buf; - out.next = NULL; + if (rc != NGX_OK) { + return rc; + } + } - rc = ngx_http_request_body_filter(r, &out); + if (rb->rest == 0 && rb->last_saved) { + break; + } - if (rc != NGX_OK) { - return rc; - } - } + if (!c->read->ready || rb->rest == 0) { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ngx_add_timer(c->read, clcf->client_body_timeout); @@ -415,6 +429,10 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r) } } + if (ngx_http_copy_pipelined_header(r, rb->buf) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + if (c->read->timer_set) { ngx_del_timer(c->read); } @@ -428,6 +446,88 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r) } +static ngx_int_t +ngx_http_copy_pipelined_header(ngx_http_request_t *r, ngx_buf_t *buf) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_http_connection_t *hc; + ngx_http_core_srv_conf_t *cscf; + + b = r->header_in; + n = buf->last - buf->pos; + + if (buf == b || n == 0) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http body pipelined header: %uz", n); + + /* + * if there is a pipelined request in the client body buffer, + * copy it to the r->header_in buffer if there is enough room, + * or allocate a large client header buffer + */ + + if (n > (size_t) (b->end - b->last)) { + + hc = r->http_connection; + + if (hc->free) { + cl = hc->free; + hc->free = cl->next; + + b = cl->buf; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http large header free: %p %uz", + b->pos, b->end - b->last); + + } else { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + b = ngx_create_temp_buf(r->connection->pool, + cscf->large_client_header_buffers.size); + if (b == NULL) { + return NGX_ERROR; + } + + cl = ngx_alloc_chain_link(r->connection->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http large header alloc: %p %uz", + b->pos, b->end - b->last); + } + + cl->next = hc->busy; + hc->busy = cl; + hc->nbusy++; + + r->header_in = b; + + if (n > (size_t) (b->end - b->last)) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "too large pipelined header after reading body"); + return NGX_ERROR; + } + } + + ngx_memcpy(b->last, buf->pos, n); + + b->last += n; + r->request_length -= n; + + return NGX_OK; +} + + static ngx_int_t ngx_http_write_request_body(ngx_http_request_t *r) { @@ -622,6 +722,7 @@ ngx_http_discarded_request_body_handler(ngx_http_request_t *r) if (rc == NGX_OK) { r->discard_body = 0; r->lingering_close = 0; + r->lingering_time = 0; ngx_http_finalize_request(r, NGX_DONE); return; } @@ -673,8 +774,7 @@ ngx_http_read_discarded_request_body(ngx_http_request_t *r) for ( ;; ) { if (r->headers_in.content_length_n == 0) { - r->read_event_handler = ngx_http_block_reading; - return NGX_OK; + break; } if (!r->connection->read->ready) { @@ -708,15 +808,24 @@ ngx_http_read_discarded_request_body(ngx_http_request_t *r) return rc; } } + + if (ngx_http_copy_pipelined_header(r, &b) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + r->read_event_handler = ngx_http_block_reading; + + return NGX_OK; } static ngx_int_t ngx_http_discard_request_body_filter(ngx_http_request_t *r, ngx_buf_t *b) { - size_t size; - ngx_int_t rc; - ngx_http_request_body_t *rb; + size_t size; + ngx_int_t rc; + ngx_http_request_body_t *rb; + ngx_http_core_srv_conf_t *cscf; if (r->headers_in.chunked) { @@ -771,7 +880,10 @@ ngx_http_discard_request_body_filter(ngx_http_request_t *r, ngx_buf_t *b) /* set amount of data we want to see next time */ - r->headers_in.content_length_n = rb->chunked->length; + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->headers_in.content_length_n = ngx_max(rb->chunked->length, + (off_t) cscf->large_client_header_buffers.size); break; } @@ -871,15 +983,32 @@ ngx_http_request_body_length_filter(ngx_http_request_t *r, ngx_chain_t *in) rb = r->request_body; + out = NULL; + ll = &out; + if (rb->rest == -1) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http request body content length filter"); rb->rest = r->headers_in.content_length_n; - } - out = NULL; - ll = &out; + if (rb->rest == 0) { + + tl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (tl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b = tl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->last_buf = 1; + + *ll = tl; + ll = &tl->next; + } + } for (cl = in; cl; cl = cl->next) { @@ -939,9 +1068,13 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in) ngx_chain_t *cl, *out, *tl, **ll; ngx_http_request_body_t *rb; ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; rb = r->request_body; + out = NULL; + ll = &out; + if (rb->rest == -1) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -952,15 +1085,16 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in) return NGX_HTTP_INTERNAL_SERVER_ERROR; } + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + r->headers_in.content_length_n = 0; - rb->rest = 3; + rb->rest = cscf->large_client_header_buffers.size; } - out = NULL; - ll = &out; - for (cl = in; cl; cl = cl->next) { + b = NULL; + for ( ;; ) { ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, @@ -995,6 +1129,29 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in) return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; } + if (b + && rb->chunked->size <= 128 + && cl->buf->last - cl->buf->pos >= rb->chunked->size) + { + r->headers_in.content_length_n += rb->chunked->size; + + if (rb->chunked->size < 8) { + + while (rb->chunked->size) { + *b->last++ = *cl->buf->pos++; + rb->chunked->size--; + } + + } else { + ngx_memmove(b->last, cl->buf->pos, rb->chunked->size); + b->last += rb->chunked->size; + cl->buf->pos += rb->chunked->size; + rb->chunked->size = 0; + } + + continue; + } + tl = ngx_chain_get_free_buf(r->pool, &rb->free); if (tl == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; @@ -1060,7 +1217,10 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in) /* set rb->rest, amount of data we want to see next time */ - rb->rest = rb->chunked->length; + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + rb->rest = ngx_max(rb->chunked->length, + (off_t) cscf->large_client_header_buffers.size); break; } @@ -1087,15 +1247,16 @@ ngx_int_t ngx_http_request_body_save_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_buf_t *b; - ngx_chain_t *cl; + ngx_chain_t *cl, *tl, **ll; ngx_http_request_body_t *rb; rb = r->request_body; -#if (NGX_DEBUG) + ll = &rb->bufs; -#if 0 for (cl = rb->bufs; cl; cl = cl->next) { + +#if 0 ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, "http body old buf t:%d f:%d %p, pos %p, size: %z " "file: %O, size: %O", @@ -1104,10 +1265,13 @@ ngx_http_request_body_save_filter(ngx_http_request_t *r, ngx_chain_t *in) cl->buf->last - cl->buf->pos, cl->buf->file_pos, cl->buf->file_last - cl->buf->file_pos); - } #endif + ll = &cl->next; + } + for (cl = in; cl; cl = cl->next) { + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, "http body new buf t:%d f:%d %p, pos %p, size: %z " "file: %O, size: %O", @@ -1116,15 +1280,17 @@ ngx_http_request_body_save_filter(ngx_http_request_t *r, ngx_chain_t *in) cl->buf->last - cl->buf->pos, cl->buf->file_pos, cl->buf->file_last - cl->buf->file_pos); - } -#endif + if (cl->buf->last_buf) { - /* TODO: coalesce neighbouring buffers */ + if (rb->last_saved) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "duplicate last buf in save filter"); + *ll = NULL; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } - if (ngx_chain_add_copy(r->pool, &rb->bufs, in) != NGX_OK) { - return NGX_HTTP_INTERNAL_SERVER_ERROR; - } + rb->last_saved = 1; #if (T_NGX_INPUT_BODY_FILTER) { @@ -1156,13 +1322,28 @@ ngx_http_request_body_save_filter(ngx_http_request_t *r, ngx_chain_t *in) } #endif + } + + tl = ngx_alloc_chain_link(r->pool); + if (tl == NULL) { + *ll = NULL; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + tl->buf = cl->buf; + *ll = tl; + ll = &tl->next; + } + + *ll = NULL; + if (r->request_body_no_buffering) { return NGX_OK; } if (rb->rest > 0) { - if (rb->buf && rb->buf->last == rb->buf->end + if (rb->bufs && rb->buf && rb->buf->last == rb->buf->end && ngx_http_write_request_body(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; @@ -1171,10 +1352,18 @@ ngx_http_request_body_save_filter(ngx_http_request_t *r, ngx_chain_t *in) return NGX_OK; } - /* rb->rest == 0 */ + if (!rb->last_saved) { + return NGX_OK; + } if (rb->temp_file || r->request_body_in_file_only) { + if (rb->bufs && rb->bufs->buf->in_file) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "body already in file"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + if (ngx_http_write_request_body(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } diff --git a/src/http/ngx_http_script.c b/src/http/ngx_http_script.c index c66ec18c02..85d68945a2 100644 --- a/src/http/ngx_http_script.c +++ b/src/http/ngx_http_script.c @@ -250,7 +250,7 @@ ngx_http_set_complex_value_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) cv = (ngx_http_complex_value_t **) (p + cmd->offset); - if (*cv != NULL) { + if (*cv != NGX_CONF_UNSET_PTR && *cv != NULL) { return "is duplicate"; } @@ -275,6 +275,44 @@ ngx_http_set_complex_value_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } +char * +ngx_http_set_complex_value_zero_slot(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + char *p = conf; + + ngx_str_t *value; + ngx_http_complex_value_t **cv; + ngx_http_compile_complex_value_t ccv; + + cv = (ngx_http_complex_value_t **) (p + cmd->offset); + + if (*cv != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + *cv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); + if (*cv == NULL) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = *cv; + ccv.zero = 1; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + char * ngx_http_set_complex_value_size_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) diff --git a/src/http/ngx_http_script.h b/src/http/ngx_http_script.h index da486750fa..8c395865bc 100644 --- a/src/http/ngx_http_script.h +++ b/src/http/ngx_http_script.h @@ -216,6 +216,8 @@ size_t ngx_http_complex_value_size(ngx_http_request_t *r, ngx_int_t ngx_http_compile_complex_value(ngx_http_compile_complex_value_t *ccv); char *ngx_http_set_complex_value_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +char *ngx_http_set_complex_value_zero_slot(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); char *ngx_http_set_complex_value_size_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); diff --git a/src/http/ngx_http_special_response.c b/src/http/ngx_http_special_response.c index e41b5b4748..b03761e305 100644 --- a/src/http/ngx_http_special_response.c +++ b/src/http/ngx_http_special_response.c @@ -648,6 +648,10 @@ ngx_http_clean_header(ngx_http_request_t *r) r->headers_out.headers.part.next = NULL; r->headers_out.headers.last = &r->headers_out.headers.part; + r->headers_out.trailers.part.nelts = 0; + r->headers_out.trailers.part.next = NULL; + r->headers_out.trailers.last = &r->headers_out.trailers.part; + r->headers_out.content_length_n = -1; r->headers_out.last_modified_time = -1; } diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index 4127a8e518..5fa59f8719 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -87,9 +87,6 @@ static void static void ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r, ngx_uint_t do_write); -static ngx_int_t ngx_http_upstream_non_buffered_filter_init(void *data); -static ngx_int_t ngx_http_upstream_non_buffered_filter(void *data, - ssize_t bytes); #if (NGX_THREADS) static ngx_int_t ngx_http_upstream_thread_handler(ngx_thread_task_t *task, ngx_file_t *file); @@ -207,6 +204,8 @@ static void ngx_http_upstream_ssl_handshake(ngx_http_request_t *, static void ngx_http_upstream_ssl_save_session(ngx_connection_t *c); static ngx_int_t ngx_http_upstream_ssl_name(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_connection_t *c); +static ngx_int_t ngx_http_upstream_ssl_certificate(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_connection_t *c); #endif #if (NGX_HTTP_UPSTREAM_RBTREE) @@ -646,6 +645,17 @@ ngx_http_upstream_init_request(ngx_http_request_t *r) u->store = u->conf->store; if (!u->store && !r->post_action && !u->conf->ignore_client_abort) { + + if (r->connection->read->ready) { + ngx_post_event(r->connection->read, &ngx_posted_events); + + } else { + if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + } + r->read_event_handler = ngx_http_upstream_rd_check_broken_connection; r->write_event_handler = ngx_http_upstream_wr_check_broken_connection; } @@ -1590,8 +1600,9 @@ static void ngx_http_upstream_connect(ngx_http_request_t *r, ngx_http_upstream_t *u) { - ngx_int_t rc; - ngx_connection_t *c; + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; r->connection->log->action = "connecting to upstream"; @@ -1724,10 +1735,12 @@ ngx_http_upstream_connect(ngx_http_request_t *r, ngx_http_upstream_t *u) /* init or reinit the ngx_output_chain() and ngx_chain_writer() contexts */ + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + u->writer.out = NULL; u->writer.last = &u->writer.out; u->writer.connection = c; - u->writer.limit = 0; + u->writer.limit = clcf->sendfile_max_chunk; if (u->request_sent) { if (ngx_http_upstream_reinit(r, u) != NGX_OK) { @@ -1834,9 +1847,6 @@ ngx_http_upstream_ssl_init_connection(ngx_http_request_t *r, return; } - c->sendfile = 0; - u->output.sendfile = 0; - if (u->conf->ssl_server_name || u->conf->ssl_verify) { if (ngx_http_upstream_ssl_name(r, u, c) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, @@ -1845,6 +1855,16 @@ ngx_http_upstream_ssl_init_connection(ngx_http_request_t *r, } } + if (u->conf->ssl_certificate && (u->conf->ssl_certificate->lengths + || u->conf->ssl_certificate_key->lengths)) + { + if (ngx_http_upstream_ssl_certificate(r, u, c) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + } + if (u->conf->ssl_session_reuse) { c->ssl->save_session = ngx_http_upstream_ssl_save_session; @@ -1932,6 +1952,11 @@ ngx_http_upstream_ssl_handshake(ngx_http_request_t *r, ngx_http_upstream_t *u, } } + if (!c->ssl->sendfile) { + c->sendfile = 0; + u->output.sendfile = 0; + } + #if (T_NGX_MULTI_UPSTREAM) if (u->multi) { ngx_http_multi_upstream_connect_init(c); @@ -2072,6 +2097,45 @@ ngx_http_upstream_ssl_name(ngx_http_request_t *r, ngx_http_upstream_t *u, return NGX_OK; } + +static ngx_int_t +ngx_http_upstream_ssl_certificate(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_connection_t *c) +{ + ngx_str_t cert, key; + + if (ngx_http_complex_value(r, u->conf->ssl_certificate, &cert) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream ssl cert: \"%s\"", cert.data); + + if (*cert.data == '\0') { + return NGX_OK; + } + + if (ngx_http_complex_value(r, u->conf->ssl_certificate_key, &key) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream ssl key: \"%s\"", key.data); + + if (ngx_ssl_connection_certificate(c, r->pool, &cert, &key, + u->conf->ssl_passwords) + != NGX_OK) + { + return NGX_ERROR; + } + + return NGX_OK; +} + #endif @@ -2087,6 +2151,7 @@ ngx_http_upstream_reinit(ngx_http_request_t *r, ngx_http_upstream_t *u) u->keepalive = 0; u->upgrade = 0; + u->error = 0; ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t)); u->headers_in.content_length_n = -1; @@ -2221,6 +2286,10 @@ ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u, c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; } + if (c->read->ready) { + ngx_post_event(c->read, &ngx_posted_events); + } + #if (T_NGX_MULTI_UPSTREAM) if (u->multi && r->connection != u->peer.connection && !r->waiting) { ngx_multi_connection_t *multi_c = ngx_get_multi_connection(c); @@ -2662,7 +2731,7 @@ ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u) #if (NGX_HTTP_CACHE) if (u->cache_status == NGX_HTTP_CACHE_EXPIRED - && ((u->conf->cache_use_stale & un->mask) || r->cache->stale_error)) + && (u->conf->cache_use_stale & un->mask)) { ngx_int_t rc; @@ -2689,6 +2758,8 @@ ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u) } #endif + + break; } #if (NGX_HTTP_CACHE) @@ -3198,9 +3269,7 @@ ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u) return; } - if (u->peer.connection->read->ready || u->length == 0) { - ngx_http_upstream_process_non_buffered_upstream(r, u); - } + ngx_http_upstream_process_non_buffered_upstream(r, u); } return; @@ -3819,7 +3888,7 @@ ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r, return; } - if (upstream->read->error) { + if (upstream->read->error || u->error) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY); return; @@ -3897,14 +3966,14 @@ ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r, } -static ngx_int_t +ngx_int_t ngx_http_upstream_non_buffered_filter_init(void *data) { return NGX_OK; } -static ngx_int_t +ngx_int_t ngx_http_upstream_non_buffered_filter(void *data, ssize_t bytes) { ngx_http_request_t *r = data; @@ -3915,6 +3984,13 @@ ngx_http_upstream_non_buffered_filter(void *data, ssize_t bytes) u = r->upstream; + if (u->length == 0) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); + return NGX_OK; + } + for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { ll = &cl->next; } @@ -3940,6 +4016,18 @@ ngx_http_upstream_non_buffered_filter(void *data, ssize_t bytes) return NGX_OK; } + if (bytes > u->length) { + + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); + + cl->buf->last = cl->buf->pos + u->length; + u->length = 0; + + return NGX_OK; + } + u->length -= bytes; return NGX_OK; @@ -3953,6 +4041,7 @@ ngx_http_upstream_thread_handler(ngx_thread_task_t *task, ngx_file_t *file) { ngx_str_t name; ngx_event_pipe_t *p; + ngx_connection_t *c; ngx_thread_pool_t *tp; ngx_http_request_t *r; ngx_http_core_loc_conf_t *clcf; @@ -3960,6 +4049,27 @@ ngx_http_upstream_thread_handler(ngx_thread_task_t *task, ngx_file_t *file) r = file->thread_ctx; p = r->upstream->pipe; + if (r->aio) { + /* + * tolerate sendfile() calls if another operation is already + * running; this can happen due to subrequests, multiple calls + * of the next body filter from a filter, or in HTTP/2 due to + * a write event on the main connection + */ + + c = r->connection; + +#if (NGX_HTTP_V2) + if (r->stream) { + c = r->stream->connection->connection; + } +#endif + + if (task == c->sendfile_task) { + return NGX_OK; + } + } + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); tp = clcf->thread_pool; @@ -4011,6 +4121,20 @@ ngx_http_upstream_thread_event_handler(ngx_event_t *ev) r->main->blocked--; r->aio = 0; +#if (NGX_HTTP_V2) + + if (r->stream) { + /* + * for HTTP/2, update write event to make sure processing will + * reach the main connection to handle sendfile() in threads + */ + + c->write->ready = 1; + c->write->active = 0; + } + +#endif + if (r->done) { /* * trigger connection event handler if the subrequest was diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h index 131be8af07..f14effcb6f 100644 --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -263,6 +263,10 @@ typedef struct { ngx_http_complex_value_t *ssl_name; ngx_flag_t ssl_server_name; ngx_flag_t ssl_verify; + + ngx_http_complex_value_t *ssl_certificate; + ngx_http_complex_value_t *ssl_certificate_key; + ngx_array_t *ssl_passwords; #endif #if (T_NGX_SSL_NTLS) @@ -429,6 +433,7 @@ struct ngx_http_upstream_s { unsigned buffering:1; unsigned keepalive:1; unsigned upgrade:1; + unsigned error:1; unsigned request_sent:1; unsigned request_body_sent:1; @@ -460,6 +465,8 @@ typedef struct { ngx_int_t ngx_http_upstream_create(ngx_http_request_t *r); void ngx_http_upstream_init(ngx_http_request_t *r); +ngx_int_t ngx_http_upstream_non_buffered_filter_init(void *data); +ngx_int_t ngx_http_upstream_non_buffered_filter(void *data, ssize_t bytes); ngx_http_upstream_srv_conf_t *ngx_http_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags); char *ngx_http_upstream_bind_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, diff --git a/src/http/ngx_http_upstream_round_robin.c b/src/http/ngx_http_upstream_round_robin.c index 6badb14b76..644eb929fa 100644 --- a/src/http/ngx_http_upstream_round_robin.c +++ b/src/http/ngx_http_upstream_round_robin.c @@ -13,8 +13,8 @@ #include "ngx_http_upstream_check_module.h" #endif -#define ngx_http_upstream_tries(p) ((p)->number \ - + ((p)->next ? (p)->next->number : 0)) +#define ngx_http_upstream_tries(p) ((p)->tries \ + + ((p)->next ? (p)->next->tries : 0)) static ngx_http_upstream_rr_peer_t *ngx_http_upstream_get_peer( @@ -35,7 +35,7 @@ ngx_http_upstream_init_round_robin(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us) { ngx_url_t u; - ngx_uint_t i, j, n, w; + ngx_uint_t i, j, n, w, t; ngx_http_upstream_server_t *server; ngx_http_upstream_rr_peer_t *peer, **peerp; ngx_http_upstream_rr_peers_t *peers, *backup; @@ -50,6 +50,7 @@ ngx_http_upstream_init_round_robin(ngx_conf_t *cf, n = 0; w = 0; + t = 0; for (i = 0; i < us->servers->nelts; i++) { if (server[i].backup) { @@ -58,6 +59,10 @@ ngx_http_upstream_init_round_robin(ngx_conf_t *cf, n += server[i].naddrs; w += server[i].naddrs * server[i].weight; + + if (!server[i].down) { + t += server[i].naddrs; + } } if (n == 0) { @@ -81,6 +86,7 @@ ngx_http_upstream_init_round_robin(ngx_conf_t *cf, peers->number = n; peers->weighted = (w != n); peers->total_weight = w; + peers->tries = t; peers->name = &us->host; #if (T_NGX_HTTP_UPSTREAM_RANDOM) peers->init_number = NGX_CONF_UNSET_UINT; @@ -135,6 +141,7 @@ ngx_http_upstream_init_round_robin(ngx_conf_t *cf, n = 0; w = 0; + t = 0; for (i = 0; i < us->servers->nelts; i++) { if (!server[i].backup) { @@ -143,6 +150,10 @@ ngx_http_upstream_init_round_robin(ngx_conf_t *cf, n += server[i].naddrs; w += server[i].naddrs * server[i].weight; + + if (!server[i].down) { + t += server[i].naddrs; + } } if (n == 0) { @@ -164,6 +175,7 @@ ngx_http_upstream_init_round_robin(ngx_conf_t *cf, backup->number = n; backup->weighted = (w != n); backup->total_weight = w; + backup->tries = t; backup->name = &us->host; #if (T_NGX_HTTP_UPSTREAM_RANDOM) backup->init_number = NGX_CONF_UNSET_UINT; @@ -258,6 +270,7 @@ ngx_http_upstream_init_round_robin(ngx_conf_t *cf, peers->number = n; peers->weighted = 0; peers->total_weight = n; + peers->tries = n; peers->name = &us->host; #if (T_NGX_HTTP_UPSTREAM_RANDOM) peers->init_number = NGX_CONF_UNSET_UINT; @@ -390,6 +403,7 @@ ngx_http_upstream_create_round_robin_peer(ngx_http_request_t *r, peers->single = (ur->naddrs == 1); peers->number = ur->naddrs; + peers->tries = ur->naddrs; #if (T_NGX_HTTP_UPSTREAM_RANDOM) peers->init_number = NGX_CONF_UNSET_UINT; #endif diff --git a/src/http/ngx_http_upstream_round_robin.h b/src/http/ngx_http_upstream_round_robin.h index cbd62bcf81..c74f3052f1 100644 --- a/src/http/ngx_http_upstream_round_robin.h +++ b/src/http/ngx_http_upstream_round_robin.h @@ -82,6 +82,7 @@ struct ngx_http_upstream_rr_peers_s { #endif ngx_uint_t total_weight; + ngx_uint_t tries; unsigned single:1; unsigned weighted:1; diff --git a/src/http/ngx_http_variables.c b/src/http/ngx_http_variables.c index d8549206ce..b9787778ec 100644 --- a/src/http/ngx_http_variables.c +++ b/src/http/ngx_http_variables.c @@ -152,6 +152,8 @@ static ngx_int_t ngx_http_variable_connection(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_connection_requests(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_connection_time(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_nginx_version(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); @@ -416,6 +418,9 @@ static ngx_http_variable_t ngx_http_core_variables[] = { { ngx_string("connection_requests"), NULL, ngx_http_variable_connection_requests, 0, 0, 0 }, + { ngx_string("connection_time"), NULL, ngx_http_variable_connection_time, + 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, + { ngx_string("nginx_version"), NULL, ngx_http_variable_nginx_version, 0, 0, 0 }, @@ -1205,7 +1210,7 @@ ngx_http_variable_argument(ngx_http_request_t *r, ngx_http_variable_value_t *v, len = name->len - (sizeof("arg_") - 1); arg = name->data + sizeof("arg_") - 1; - if (ngx_http_arg(r, arg, len, &value) != NGX_OK) { + if (len == 0 || ngx_http_arg(r, arg, len, &value) != NGX_OK) { v->not_found = 1; return NGX_OK; } @@ -1304,6 +1309,10 @@ ngx_http_variable_content_length(ngx_http_request_t *r, v->no_cacheable = 0; v->not_found = 0; + } else if (r->headers_in.chunked) { + v->not_found = 1; + v->no_cacheable = 1; + } else { v->not_found = 1; } @@ -2382,6 +2391,31 @@ ngx_http_variable_connection_requests(ngx_http_request_t *r, } +static ngx_int_t +ngx_http_variable_connection_time(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + ngx_msec_int_t ms; + + p = ngx_pnalloc(r->pool, NGX_TIME_T_LEN + 4); + if (p == NULL) { + return NGX_ERROR; + } + + ms = ngx_current_msec - r->connection->start_time; + ms = ngx_max(ms, 0); + + v->len = ngx_sprintf(p, "%T.%03M", (time_t) ms / 1000, ms % 1000) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NGX_OK; +} + + static ngx_int_t ngx_http_variable_nginx_version(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) diff --git a/src/http/ngx_http_write_filter_module.c b/src/http/ngx_http_write_filter_module.c index 399f5e65cd..ec5b833cc5 100644 --- a/src/http/ngx_http_write_filter_module.c +++ b/src/http/ngx_http_write_filter_module.c @@ -333,18 +333,13 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in) delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate); if (delay > 0) { - limit = 0; c->write->delayed = 1; ngx_add_timer(c->write, delay); } } - if (limit - && c->write->ready - && c->sent - sent >= limit - (off_t) (2 * ngx_pagesize)) - { - c->write->delayed = 1; - ngx_add_timer(c->write, 1); + if (chain && c->write->ready && !c->write->delayed) { + ngx_post_event(c->write, &ngx_posted_next_events); } for (cl = r->out; cl && cl != chain; /* void */) { diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 8b0fc53b0d..0e45a7b27b 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -60,6 +60,8 @@ typedef struct { static void ngx_http_v2_read_handler(ngx_event_t *rev); static void ngx_http_v2_write_handler(ngx_event_t *wev); static void ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c); +static void ngx_http_v2_lingering_close(ngx_connection_t *c); +static void ngx_http_v2_lingering_close_handler(ngx_event_t *rev); static u_char *ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); @@ -171,7 +173,7 @@ static ngx_int_t ngx_http_v2_construct_cookie_header(ngx_http_request_t *r); static void ngx_http_v2_run_request(ngx_http_request_t *r); static void ngx_http_v2_run_request_handler(ngx_event_t *ev); static ngx_int_t ngx_http_v2_process_request_body(ngx_http_request_t *r, - u_char *pos, size_t size, ngx_uint_t last); + u_char *pos, size_t size, ngx_uint_t last, ngx_uint_t flush); static ngx_int_t ngx_http_v2_filter_request_body(ngx_http_request_t *r); static void ngx_http_v2_read_client_request_body_handler(ngx_http_request_t *r); @@ -236,6 +238,7 @@ ngx_http_v2_init(ngx_event_t *rev) ngx_http_v2_srv_conf_t *h2scf; ngx_http_v2_main_conf_t *h2mcf; ngx_http_v2_connection_t *h2c; + ngx_http_core_srv_conf_t *cscf; c = rev->data; hc = c->data; @@ -274,7 +277,7 @@ ngx_http_v2_init(ngx_event_t *rev) h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); h2c->concurrent_pushes = h2scf->concurrent_pushes; - h2c->priority_limit = h2scf->concurrent_streams; + h2c->priority_limit = ngx_max(h2scf->concurrent_streams, 100); h2c->pool = ngx_create_pool(h2scf->pool_size, h2c->connection->log); if (h2c->pool == NULL) { @@ -323,7 +326,14 @@ ngx_http_v2_init(ngx_event_t *rev) rev->handler = ngx_http_v2_read_handler; c->write->handler = ngx_http_v2_write_handler; + if (!rev->timer_set) { + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, + ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + } + c->idle = 1; + ngx_reusable_connection(c, 0); ngx_http_v2_read_handler(rev); } @@ -360,6 +370,11 @@ ngx_http_v2_read_handler(ngx_event_t *rev) return; } + if (!h2c->processing && !h2c->pushing) { + ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_NO_ERROR); + return; + } + if (!h2c->goaway) { h2c->goaway = 1; @@ -447,14 +462,6 @@ ngx_http_v2_read_handler(ngx_event_t *rev) h2c->blocked = 0; - if (h2c->processing || h2c->pushing) { - if (rev->timer_set) { - ngx_del_timer(rev); - } - - return; - } - ngx_http_v2_handle_connection(h2c); } @@ -473,6 +480,7 @@ ngx_http_v2_write_handler(ngx_event_t *wev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 write event timed out"); c->error = 1; + c->timedout = 1; ngx_http_v2_finalize_connection(h2c, 0); return; } @@ -626,9 +634,9 @@ ngx_http_v2_send_output_queue(ngx_http_v2_connection_t *h2c) static void ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c) { - ngx_int_t rc; - ngx_connection_t *c; - ngx_http_v2_srv_conf_t *h2scf; + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; if (h2c->last_out || h2c->processing || h2c->pushing) { return; @@ -661,14 +669,20 @@ ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c) } if (h2c->goaway) { - ngx_http_close_connection(c); + ngx_http_v2_lingering_close(c); return; } - h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, - ngx_http_v2_module); + clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, + ngx_http_core_module); + + if (!c->read->timer_set) { + ngx_add_timer(c->read, clcf->keepalive_timeout); + } + + ngx_reusable_connection(c, 1); + if (h2c->state.incomplete) { - ngx_add_timer(c->read, h2scf->recv_timeout); return; } @@ -686,7 +700,6 @@ ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c) #endif c->destroyed = 1; - ngx_reusable_connection(c, 1); c->write->handler = ngx_http_empty_handler; c->read->handler = ngx_http_v2_idle_handler; @@ -694,8 +707,142 @@ ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c) if (c->write->timer_set) { ngx_del_timer(c->write); } +} + + +static void +ngx_http_v2_lingering_close(ngx_connection_t *c) +{ + ngx_event_t *rev, *wev; + ngx_http_v2_connection_t *h2c; + ngx_http_core_loc_conf_t *clcf; + + h2c = c->data; + + clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, + ngx_http_core_module); + + if (clcf->lingering_close == NGX_HTTP_LINGERING_OFF) { + ngx_http_close_connection(c); + return; + } + + if (h2c->lingering_time == 0) { + h2c->lingering_time = ngx_time() + + (time_t) (clcf->lingering_time / 1000); + } - ngx_add_timer(c->read, h2scf->idle_timeout); +#if (NGX_HTTP_SSL) + if (c->ssl) { + ngx_int_t rc; + + rc = ngx_ssl_shutdown(c); + + if (rc == NGX_ERROR) { + ngx_http_close_connection(c); + return; + } + + if (rc == NGX_AGAIN) { + c->ssl->handler = ngx_http_v2_lingering_close; + return; + } + } +#endif + + rev = c->read; + rev->handler = ngx_http_v2_lingering_close_handler; + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + wev = c->write; + wev->handler = ngx_http_empty_handler; + + if (wev->active && (ngx_event_flags & NGX_USE_LEVEL_EVENT)) { + if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + } + + if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) { + ngx_connection_error(c, ngx_socket_errno, + ngx_shutdown_socket_n " failed"); + ngx_http_close_connection(c); + return; + } + + c->close = 0; + ngx_reusable_connection(c, 1); + + ngx_add_timer(rev, clcf->lingering_timeout); + + if (rev->ready) { + ngx_http_v2_lingering_close_handler(rev); + } +} + + +static void +ngx_http_v2_lingering_close_handler(ngx_event_t *rev) +{ + ssize_t n; + ngx_msec_t timer; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; + ngx_http_v2_connection_t *h2c; + u_char buffer[NGX_HTTP_LINGERING_BUFFER_SIZE]; + + c = rev->data; + h2c = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http2 lingering close handler"); + + if (rev->timedout || c->close) { + ngx_http_close_connection(c); + return; + } + + timer = (ngx_msec_t) h2c->lingering_time - (ngx_msec_t) ngx_time(); + if ((ngx_msec_int_t) timer <= 0) { + ngx_http_close_connection(c); + return; + } + + do { + n = c->recv(c, buffer, NGX_HTTP_LINGERING_BUFFER_SIZE); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "lingering read: %z", n); + + if (n == NGX_AGAIN) { + break; + } + + if (n == NGX_ERROR || n == 0) { + ngx_http_close_connection(c); + return; + } + + } while (rev->ready); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, + ngx_http_core_module); + timer *= 1000; + + if (timer > clcf->lingering_timeout) { + timer = clcf->lingering_timeout; + } + + ngx_add_timer(rev, timer); } @@ -731,9 +878,8 @@ ngx_http_v2_state_preface(ngx_http_v2_connection_t *h2c, u_char *pos, } if (ngx_memcmp(pos, preface, sizeof(preface) - 1) != 0) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "invalid http2 connection preface \"%*s\"", - sizeof(preface) - 1, pos); + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "invalid connection preface"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } @@ -754,9 +900,8 @@ ngx_http_v2_state_preface_end(ngx_http_v2_connection_t *h2c, u_char *pos, } if (ngx_memcmp(pos, preface, sizeof(preface) - 1) != 0) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "invalid http2 connection preface \"%*s\"", - sizeof(preface) - 1, pos); + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "invalid connection preface"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); } @@ -845,6 +990,13 @@ ngx_http_v2_state_data(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 DATA frame"); + if (h2c->state.sid == 0) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent DATA frame with incorrect identifier"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + if (size > h2c->recv_window) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client violated connection flow control: " @@ -943,6 +1095,7 @@ ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, u_char *pos, size_t size; ngx_buf_t *buf; ngx_int_t rc; + ngx_connection_t *fc; ngx_http_request_t *r; ngx_http_v2_stream_t *stream; ngx_http_v2_srv_conf_t *h2scf; @@ -961,6 +1114,7 @@ ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, u_char *pos, } r = stream->request; + fc = r->connection; if (r->reading_body && !r->request_body_no_buffering) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, @@ -969,6 +1123,13 @@ ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, u_char *pos, return ngx_http_v2_state_skip_padded(h2c, pos, end); } + if (r->headers_in.content_length_n < 0 && !r->headers_in.chunked) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "skipping http2 DATA frame"); + + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + size = end - pos; if (size >= h2c->state.length) { @@ -979,13 +1140,16 @@ ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, u_char *pos, h2c->payload_bytes += size; if (r->request_body) { - rc = ngx_http_v2_process_request_body(r, pos, size, stream->in_closed); + rc = ngx_http_v2_process_request_body(r, pos, size, + stream->in_closed, 0); - if (rc != NGX_OK) { + if (rc != NGX_OK && rc != NGX_AGAIN) { stream->skip_data = 1; ngx_http_finalize_request(r, rc); } + ngx_http_run_posted_requests(fc); + } else if (size) { buf = stream->preread; @@ -1031,12 +1195,15 @@ static u_char * ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { - size_t size; - ngx_uint_t padded, priority, depend, dependency, excl, weight; - ngx_uint_t status; - ngx_http_v2_node_t *node; - ngx_http_v2_stream_t *stream; - ngx_http_v2_srv_conf_t *h2scf; + size_t size; + ngx_uint_t padded, priority, depend, dependency, excl, + weight; + ngx_uint_t status; + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + ngx_http_v2_srv_conf_t *h2scf; + ngx_http_core_srv_conf_t *cscf; + ngx_http_core_loc_conf_t *clcf; padded = h2c->state.flags & NGX_HTTP_V2_PADDED_FLAG; priority = h2c->state.flags & NGX_HTTP_V2_PRIORITY_FLAG; @@ -1137,11 +1304,15 @@ ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } + cscf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_core_module); + + h2c->state.header_limit = cscf->large_client_header_buffers.size + * cscf->large_client_header_buffers.num; + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, ngx_http_v2_module); - h2c->state.header_limit = h2scf->max_header_size; - if (h2c->processing >= h2scf->concurrent_streams) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "concurrent streams exceeded %ui", h2c->processing); @@ -1195,7 +1366,14 @@ ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, ngx_http_v2_set_dependency(h2c, node, depend, excl); } - if (h2c->connection->requests >= h2scf->max_requests) { + clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, + ngx_http_core_module); + + if (clcf->keepalive_timeout == 0 + || h2c->connection->requests >= clcf->keepalive_requests + || ngx_current_msec - h2c->connection->start_time + > clcf->keepalive_time) + { h2c->goaway = 1; if (ngx_http_v2_send_goaway(h2c, NGX_HTTP_V2_NO_ERROR) == NGX_ERROR) { @@ -1320,10 +1498,10 @@ static u_char * ngx_http_v2_state_field_len(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { - size_t alloc; - ngx_int_t len; - ngx_uint_t huff; - ngx_http_v2_srv_conf_t *h2scf; + size_t alloc; + ngx_int_t len; + ngx_uint_t huff; + ngx_http_core_srv_conf_t *cscf; if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) && h2c->state.length < NGX_HTTP_V2_INT_OCTETS) @@ -1370,12 +1548,12 @@ ngx_http_v2_state_field_len(ngx_http_v2_connection_t *h2c, u_char *pos, "http2 %s string, len:%i", huff ? "encoded" : "raw", len); - h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, - ngx_http_v2_module); + cscf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_core_module); - if ((size_t) len > h2scf->max_field_size) { + if ((size_t) len > cscf->large_client_header_buffers.size) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, - "client exceeded http2_max_field_size limit"); + "client sent too large header field"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_ENHANCE_YOUR_CALM); } @@ -1422,10 +1600,10 @@ ngx_http_v2_state_field_huff(ngx_http_v2_connection_t *h2c, u_char *pos, h2c->state.length -= size; h2c->state.field_rest -= size; - if (ngx_http_v2_huff_decode(&h2c->state.field_state, pos, size, - &h2c->state.field_end, - h2c->state.field_rest == 0, - h2c->connection->log) + if (ngx_http_huff_decode(&h2c->state.field_state, pos, size, + &h2c->state.field_end, + h2c->state.field_rest == 0, + h2c->connection->log) != NGX_OK) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, @@ -1590,7 +1768,7 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, if (len > h2c->state.header_limit) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, - "client exceeded http2_max_header_size limit"); + "client sent too large header"); return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_ENHANCE_YOUR_CALM); } @@ -1987,6 +2165,16 @@ static u_char * ngx_http_v2_state_settings(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 SETTINGS frame"); + + if (h2c->state.sid) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent SETTINGS frame with incorrect identifier"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + if (h2c->state.flags == NGX_HTTP_V2_ACK_FLAG) { if (h2c->state.length != 0) { @@ -2010,9 +2198,6 @@ ngx_http_v2_state_settings(ngx_http_v2_connection_t *h2c, u_char *pos, return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "http2 SETTINGS frame"); - return ngx_http_v2_state_settings_params(h2c, pos, end); } @@ -2161,6 +2346,13 @@ ngx_http_v2_state_ping(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 PING frame"); + if (h2c->state.sid) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent PING frame with incorrect identifier"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + if (h2c->state.flags & NGX_HTTP_V2_ACK_FLAG) { return ngx_http_v2_state_skip(h2c, pos, end); } @@ -2202,6 +2394,13 @@ ngx_http_v2_state_goaway(ngx_http_v2_connection_t *h2c, u_char *pos, return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_goaway); } + if (h2c->state.sid) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent GOAWAY frame with incorrect identifier"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + #if (NGX_DEBUG) h2c->state.length -= NGX_HTTP_V2_GOAWAY_SIZE; @@ -3103,6 +3302,10 @@ ngx_http_v2_create_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t push) h2c->priority_limit += h2scf->concurrent_streams; + if (h2c->connection->read->timer_set) { + ngx_del_timer(h2c->connection->read); + } + return stream; } @@ -3255,7 +3458,7 @@ ngx_http_v2_validate_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) continue; } - if (ch == '\0' || ch == LF || ch == CR || ch == ':' + if (ch <= 0x20 || ch == 0x7f || ch == ':' || (ch >= 'A' && ch <= 'Z')) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, @@ -3404,7 +3607,8 @@ ngx_http_v2_parse_method(ngx_http_request_t *r, ngx_str_t *value) { 4, "LOCK", NGX_HTTP_LOCK }, { 6, "UNLOCK", NGX_HTTP_UNLOCK }, { 5, "PATCH", NGX_HTTP_PATCH }, - { 5, "TRACE", NGX_HTTP_TRACE } + { 5, "TRACE", NGX_HTTP_TRACE }, + { 7, "CONNECT", NGX_HTTP_CONNECT } }, *test; if (r->method_name.len) { @@ -3824,16 +4028,30 @@ ngx_http_v2_read_request_body(ngx_http_request_t *r) return NGX_OK; } + rb->rest = 1; + + /* set rb->filter_need_buffering */ + + rc = ngx_http_top_request_body_filter(r, NULL); + + if (rc != NGX_OK) { + stream->skip_data = 1; + return rc; + } + h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module); clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); len = r->headers_in.content_length_n; - if (r->request_body_no_buffering && !stream->in_closed) { + if (len < 0 || len > (off_t) clcf->client_body_buffer_size) { + len = clcf->client_body_buffer_size; - if (len < 0 || len > (off_t) clcf->client_body_buffer_size) { - len = clcf->client_body_buffer_size; - } + } else { + len++; + } + + if (r->request_body_no_buffering || rb->filter_need_buffering) { /* * We need a room to store data up to the stream's initial window size, @@ -3847,57 +4065,54 @@ ngx_http_v2_read_request_body(ngx_http_request_t *r) if (len > NGX_HTTP_V2_MAX_WINDOW) { len = NGX_HTTP_V2_MAX_WINDOW; } - - rb->buf = ngx_create_temp_buf(r->pool, (size_t) len); - - } else if (len >= 0 && len <= (off_t) clcf->client_body_buffer_size - && !r->request_body_in_file_only) - { - rb->buf = ngx_create_temp_buf(r->pool, (size_t) len); - - } else { - rb->buf = ngx_calloc_buf(r->pool); - - if (rb->buf != NULL) { - rb->buf->sync = 1; - } } + rb->buf = ngx_create_temp_buf(r->pool, (size_t) len); + if (rb->buf == NULL) { stream->skip_data = 1; return NGX_HTTP_INTERNAL_SERVER_ERROR; } - rb->rest = 1; - buf = stream->preread; if (stream->in_closed) { - r->request_body_no_buffering = 0; + if (!rb->filter_need_buffering) { + r->request_body_no_buffering = 0; + } if (buf) { rc = ngx_http_v2_process_request_body(r, buf->pos, - buf->last - buf->pos, 1); + buf->last - buf->pos, 1, 0); ngx_pfree(r->pool, buf->start); + + } else { + rc = ngx_http_v2_process_request_body(r, NULL, 0, 1, 0); + } + + if (rc != NGX_AGAIN) { return rc; } - return ngx_http_v2_process_request_body(r, NULL, 0, 1); + r->read_event_handler = ngx_http_v2_read_client_request_body_handler; + r->write_event_handler = ngx_http_request_empty_handler; + + return NGX_AGAIN; } if (buf) { rc = ngx_http_v2_process_request_body(r, buf->pos, - buf->last - buf->pos, 0); + buf->last - buf->pos, 0, 0); ngx_pfree(r->pool, buf->start); - if (rc != NGX_OK) { + if (rc != NGX_OK && rc != NGX_AGAIN) { stream->skip_data = 1; return rc; } } - if (r->request_body_no_buffering) { + if (r->request_body_no_buffering || rb->filter_need_buffering) { size = (size_t) len - h2scf->preread_size; } else { @@ -3939,9 +4154,9 @@ ngx_http_v2_read_request_body(ngx_http_request_t *r) static ngx_int_t ngx_http_v2_process_request_body(ngx_http_request_t *r, u_char *pos, - size_t size, ngx_uint_t last) + size_t size, ngx_uint_t last, ngx_uint_t flush) { - ngx_buf_t *buf; + size_t n; ngx_int_t rc; ngx_connection_t *fc; ngx_http_request_body_t *rb; @@ -3949,77 +4164,122 @@ ngx_http_v2_process_request_body(ngx_http_request_t *r, u_char *pos, fc = r->connection; rb = r->request_body; - buf = rb->buf; - if (size) { - if (buf->sync) { - buf->pos = buf->start = pos; - buf->last = buf->end = pos + size; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 process request body"); - r->request_body_in_file_only = 1; + if (size == 0 && !last && !flush) { + return NGX_AGAIN; + } - } else { - if (size > (size_t) (buf->end - buf->last)) { - ngx_log_error(NGX_LOG_INFO, fc->log, 0, - "client intended to send body data " - "larger than declared"); + for ( ;; ) { + for ( ;; ) { + if (rb->buf->last == rb->buf->end && size) { - return NGX_HTTP_BAD_REQUEST; + if (r->request_body_no_buffering) { + + /* should never happen due to flow control */ + + ngx_log_error(NGX_LOG_ALERT, fc->log, 0, + "no space in http2 body buffer"); + + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + /* update chains */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 body update chains"); + + rc = ngx_http_v2_filter_request_body(r); + + if (rc != NGX_OK) { + return rc; + } + + if (rb->busy != NULL) { + ngx_log_error(NGX_LOG_ALERT, fc->log, 0, + "busy buffers after request body flush"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + rb->buf->pos = rb->buf->start; + rb->buf->last = rb->buf->start; } - buf->last = ngx_cpymem(buf->last, pos, size); - } - } + /* copy body data to the buffer */ - if (last) { - rb->rest = 0; + n = rb->buf->end - rb->buf->last; - if (fc->read->timer_set) { - ngx_del_timer(fc->read); - } + if (n > size) { + n = size; + } - if (r->request_body_no_buffering) { - ngx_post_event(fc->read, &ngx_posted_events); - return NGX_OK; - } + if (n > 0) { + rb->buf->last = ngx_cpymem(rb->buf->last, pos, n); + } - rc = ngx_http_v2_filter_request_body(r); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 request body recv %uz", n); - if (rc != NGX_OK) { - return rc; + pos += n; + size -= n; + + if (size == 0 && last) { + rb->rest = 0; + } + + if (size == 0) { + break; + } } - if (buf->sync) { - /* prevent reusing this buffer in the upstream module */ - rb->buf = NULL; + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 request body rest %O", rb->rest); + + if (flush) { + rc = ngx_http_v2_filter_request_body(r); + + if (rc != NGX_OK) { + return rc; + } } - if (r->headers_in.chunked) { - r->headers_in.content_length_n = rb->received; + if (rb->rest == 0 && rb->last_saved) { + break; } - r->read_event_handler = ngx_http_block_reading; - rb->post_handler(r); + if (size == 0) { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + ngx_add_timer(fc->read, clcf->client_body_timeout); - return NGX_OK; - } + if (!flush) { + ngx_post_event(fc->read, &ngx_posted_events); + } - if (size == 0) { - return NGX_OK; + return NGX_AGAIN; + } } - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - ngx_add_timer(fc->read, clcf->client_body_timeout); + if (fc->read->timer_set) { + ngx_del_timer(fc->read); + } if (r->request_body_no_buffering) { - ngx_post_event(fc->read, &ngx_posted_events); + if (!flush) { + ngx_post_event(fc->read, &ngx_posted_events); + } + return NGX_OK; } - if (buf->sync) { - return ngx_http_v2_filter_request_body(r); + if (r->headers_in.chunked) { + r->headers_in.content_length_n = rb->received; } + r->read_event_handler = ngx_http_block_reading; + rb->post_handler(r); + return NGX_OK; } @@ -4036,7 +4296,7 @@ ngx_http_v2_filter_request_body(ngx_http_request_t *r) rb = r->request_body; buf = rb->buf; - if (buf->pos == buf->last && rb->rest) { + if (buf->pos == buf->last && (rb->rest || rb->last_sent)) { cl = NULL; goto update; } @@ -4099,6 +4359,7 @@ ngx_http_v2_filter_request_body(ngx_http_request_t *r) } b->last_buf = 1; + rb->last_sent = 1; } b->tag = (ngx_buf_tag_t) &ngx_http_v2_filter_request_body; @@ -4118,7 +4379,12 @@ ngx_http_v2_filter_request_body(ngx_http_request_t *r) static void ngx_http_v2_read_client_request_body_handler(ngx_http_request_t *r) { - ngx_connection_t *fc; + size_t window; + ngx_buf_t *buf; + ngx_int_t rc; + ngx_connection_t *fc; + ngx_http_v2_stream_t *stream; + ngx_http_v2_connection_t *h2c; fc = r->connection; @@ -4144,6 +4410,75 @@ ngx_http_v2_read_client_request_body_handler(ngx_http_request_t *r) ngx_http_finalize_request(r, NGX_HTTP_CLIENT_CLOSED_REQUEST); return; } + + rc = ngx_http_v2_process_request_body(r, NULL, 0, r->stream->in_closed, 1); + + if (rc != NGX_OK && rc != NGX_AGAIN) { + r->stream->skip_data = 1; + ngx_http_finalize_request(r, rc); + return; + } + + if (rc == NGX_OK) { + return; + } + + if (r->stream->no_flow_control) { + return; + } + + if (r->request_body->rest == 0) { + return; + } + + if (r->request_body->busy != NULL) { + return; + } + + stream = r->stream; + h2c = stream->connection; + + buf = r->request_body->buf; + + buf->pos = buf->start; + buf->last = buf->start; + + window = buf->end - buf->start; + + if (h2c->state.stream == stream) { + window -= h2c->state.length; + } + + if (window <= stream->recv_window) { + if (window < stream->recv_window) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "http2 negative window update"); + + stream->skip_data = 1; + + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + return; + } + + if (ngx_http_v2_send_window_update(h2c, stream->node->id, + window - stream->recv_window) + == NGX_ERROR) + { + stream->skip_data = 1; + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + stream->recv_window = window; + + if (ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) { + stream->skip_data = 1; + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } } @@ -4156,11 +4491,13 @@ ngx_http_v2_read_unbuffered_request_body(ngx_http_request_t *r) ngx_connection_t *fc; ngx_http_v2_stream_t *stream; ngx_http_v2_connection_t *h2c; - ngx_http_core_loc_conf_t *clcf; stream = r->stream; fc = r->connection; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 read unbuffered request body"); + if (fc->read->timedout) { if (stream->recv_window) { stream->skip_data = 1; @@ -4177,17 +4514,21 @@ ngx_http_v2_read_unbuffered_request_body(ngx_http_request_t *r) return NGX_HTTP_BAD_REQUEST; } - rc = ngx_http_v2_filter_request_body(r); + rc = ngx_http_v2_process_request_body(r, NULL, 0, r->stream->in_closed, 1); - if (rc != NGX_OK) { + if (rc != NGX_OK && rc != NGX_AGAIN) { stream->skip_data = 1; return rc; } - if (!r->request_body->rest) { + if (rc == NGX_OK) { return NGX_OK; } + if (r->request_body->rest == 0) { + return NGX_AGAIN; + } + if (r->request_body->busy != NULL) { return NGX_AGAIN; } @@ -4228,11 +4569,6 @@ ngx_http_v2_read_unbuffered_request_body(ngx_http_request_t *r) return NGX_HTTP_INTERNAL_SERVER_ERROR; } - if (stream->recv_window == 0) { - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - ngx_add_timer(fc->read, clcf->client_body_timeout); - } - stream->recv_window = window; return NGX_AGAIN; @@ -4469,6 +4805,7 @@ ngx_http_v2_idle_handler(ngx_event_t *rev) ngx_connection_t *c; ngx_http_v2_srv_conf_t *h2scf; ngx_http_v2_connection_t *h2c; + ngx_http_core_loc_conf_t *clcf; c = rev->data; h2c = c->data; @@ -4500,10 +4837,10 @@ ngx_http_v2_idle_handler(ngx_event_t *rev) #endif - h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, - ngx_http_v2_module); + clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, + ngx_http_core_module); - if (h2c->idle++ > 10 * h2scf->max_requests) { + if (h2c->idle++ > 10 * clcf->keepalive_requests) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "http2 flood detected"); ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_NO_ERROR); @@ -4513,6 +4850,9 @@ ngx_http_v2_idle_handler(ngx_event_t *rev) c->destroyed = 0; ngx_reusable_connection(c, 0); + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + h2c->pool = ngx_create_pool(h2scf->pool_size, h2c->connection->log); if (h2c->pool == NULL) { ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_INTERNAL_ERROR); @@ -4543,16 +4883,15 @@ ngx_http_v2_finalize_connection(ngx_http_v2_connection_t *h2c, h2c->blocked = 1; if (!c->error && !h2c->goaway) { + h2c->goaway = 1; + if (ngx_http_v2_send_goaway(h2c, status) != NGX_ERROR) { (void) ngx_http_v2_send_output_queue(h2c); } } - c->error = 1; - if (!h2c->processing && !h2c->pushing) { - ngx_http_close_connection(c); - return; + goto done; } c->read->handler = ngx_http_empty_handler; @@ -4600,10 +4939,18 @@ ngx_http_v2_finalize_connection(ngx_http_v2_connection_t *h2c, h2c->blocked = 0; if (h2c->processing || h2c->pushing) { + c->error = 1; + return; + } + +done: + + if (c->error) { + ngx_http_close_connection(c); return; } - ngx_http_close_connection(c); + ngx_http_v2_lingering_close(c); } diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h index 59ddf54e2c..70ee287ae9 100644 --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h @@ -13,8 +13,7 @@ #include -#define NGX_HTTP_V2_ALPN_ADVERTISE "\x02h2" -#define NGX_HTTP_V2_NPN_ADVERTISE NGX_HTTP_V2_ALPN_ADVERTISE +#define NGX_HTTP_V2_ALPN_PROTO "\x02h2" #define NGX_HTTP_V2_STATE_BUFFER_SIZE 16 @@ -157,6 +156,8 @@ struct ngx_http_v2_connection_s { ngx_uint_t last_sid; ngx_uint_t last_push; + time_t lingering_time; + unsigned closed_nodes:8; unsigned settings_ack:1; unsigned table_update:1; @@ -310,12 +311,6 @@ ngx_int_t ngx_http_v2_add_header(ngx_http_v2_connection_t *h2c, ngx_int_t ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size); -ngx_int_t ngx_http_v2_huff_decode(u_char *state, u_char *src, size_t len, - u_char **dst, ngx_uint_t last, ngx_log_t *log); -size_t ngx_http_v2_huff_encode(u_char *src, size_t len, u_char *dst, - ngx_uint_t lower); - - #define ngx_http_v2_prefix(bits) ((1 << (bits)) - 1) diff --git a/src/http/v2/ngx_http_v2_encode.c b/src/http/v2/ngx_http_v2_encode.c index ac792084e1..8798aa9ae2 100644 --- a/src/http/v2/ngx_http_v2_encode.c +++ b/src/http/v2/ngx_http_v2_encode.c @@ -20,7 +20,7 @@ ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp, { size_t hlen; - hlen = ngx_http_v2_huff_encode(src, len, tmp, lower); + hlen = ngx_http_huff_encode(src, len, tmp, lower); if (hlen > 0) { *dst = NGX_HTTP_V2_ENCODE_HUFF; diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c index be3d017434..a8d9607cda 100644 --- a/src/http/v2/ngx_http_v2_filter_module.c +++ b/src/http/v2/ngx_http_v2_filter_module.c @@ -1456,6 +1456,9 @@ ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit) size = 0; #endif + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 send chain: %p", in); + while (in) { size = ngx_buf_size(in->buf); @@ -1474,12 +1477,8 @@ ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit) return NGX_CHAIN_ERROR; } - if (stream->queued) { - fc->write->active = 1; - fc->write->ready = 0; - - } else { - fc->buffered &= ~NGX_HTTP_V2_BUFFERED; + if (ngx_http_v2_filter_send(fc, stream) == NGX_ERROR) { + return NGX_CHAIN_ERROR; } return NULL; @@ -1488,9 +1487,16 @@ ngx_http_v2_send_chain(ngx_connection_t *fc, ngx_chain_t *in, off_t limit) h2c = stream->connection; if (size && ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) { - fc->write->active = 1; - fc->write->ready = 0; - return in; + + if (ngx_http_v2_filter_send(fc, stream) == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + + if (ngx_http_v2_flow_control(h2c, stream) == NGX_DECLINED) { + fc->write->active = 1; + fc->write->ready = 0; + return in; + } } if (in->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_filter_get_shadow) { @@ -1833,6 +1839,11 @@ ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c, static ngx_inline ngx_int_t ngx_http_v2_filter_send(ngx_connection_t *fc, ngx_http_v2_stream_t *stream) { + if (stream->queued == 0) { + fc->buffered &= ~NGX_HTTP_V2_BUFFERED; + return NGX_OK; + } + stream->blocked = 1; if (ngx_http_v2_send_output_queue(stream->connection) == NGX_ERROR) { diff --git a/src/http/v2/ngx_http_v2_module.c b/src/http/v2/ngx_http_v2_module.c index 67de47158b..907f0167c9 100644 --- a/src/http/v2/ngx_http_v2_module.c +++ b/src/http/v2/ngx_http_v2_module.c @@ -36,10 +36,31 @@ static char *ngx_http_v2_preread_size(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_v2_streams_index_mask(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_v2_chunk_size(ngx_conf_t *cf, void *post, void *data); -static char *ngx_http_v2_spdy_deprecated(ngx_conf_t *cf, ngx_command_t *cmd, +static char *ngx_http_v2_obsolete(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static ngx_conf_deprecated_t ngx_http_v2_recv_timeout_deprecated = { + ngx_conf_deprecated, "http2_recv_timeout", "client_header_timeout" +}; + +static ngx_conf_deprecated_t ngx_http_v2_idle_timeout_deprecated = { + ngx_conf_deprecated, "http2_idle_timeout", "keepalive_timeout" +}; + +static ngx_conf_deprecated_t ngx_http_v2_max_requests_deprecated = { + ngx_conf_deprecated, "http2_max_requests", "keepalive_requests" +}; + +static ngx_conf_deprecated_t ngx_http_v2_max_field_size_deprecated = { + ngx_conf_deprecated, "http2_max_field_size", "large_client_header_buffers" +}; + +static ngx_conf_deprecated_t ngx_http_v2_max_header_size_deprecated = { + ngx_conf_deprecated, "http2_max_header_size", "large_client_header_buffers" +}; + + static ngx_conf_post_t ngx_http_v2_recv_buffer_size_post = { ngx_http_v2_recv_buffer_size }; static ngx_conf_post_t ngx_http_v2_pool_size_post = @@ -84,24 +105,24 @@ static ngx_command_t ngx_http_v2_commands[] = { { ngx_string("http2_max_requests"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v2_srv_conf_t, max_requests), - NULL }, + ngx_http_v2_obsolete, + 0, + 0, + &ngx_http_v2_max_requests_deprecated }, { ngx_string("http2_max_field_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v2_srv_conf_t, max_field_size), - NULL }, + ngx_http_v2_obsolete, + 0, + 0, + &ngx_http_v2_max_field_size_deprecated }, { ngx_string("http2_max_header_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v2_srv_conf_t, max_header_size), - NULL }, + ngx_http_v2_obsolete, + 0, + 0, + &ngx_http_v2_max_header_size_deprecated }, { ngx_string("http2_body_preread_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, @@ -119,17 +140,17 @@ static ngx_command_t ngx_http_v2_commands[] = { { ngx_string("http2_recv_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_msec_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v2_srv_conf_t, recv_timeout), - NULL }, + ngx_http_v2_obsolete, + 0, + 0, + &ngx_http_v2_recv_timeout_deprecated }, { ngx_string("http2_idle_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_msec_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v2_srv_conf_t, idle_timeout), - NULL }, + ngx_http_v2_obsolete, + 0, + 0, + &ngx_http_v2_idle_timeout_deprecated }, { ngx_string("http2_chunk_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, @@ -152,62 +173,6 @@ static ngx_command_t ngx_http_v2_commands[] = { 0, NULL }, - { ngx_string("spdy_recv_buffer_size"), - NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_MAIN_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_pool_size"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_max_concurrent_streams"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_streams_index_size"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_recv_timeout"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_keepalive_timeout"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_headers_comp"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("spdy_chunk_size"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_http_v2_spdy_deprecated, - NGX_HTTP_LOC_CONF_OFFSET, - 0, - NULL }, - #if (T_NGX_HTTP2_SRV_ENABLE) { ngx_string("http2"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, @@ -362,18 +327,11 @@ ngx_http_v2_create_srv_conf(ngx_conf_t *cf) h2scf->concurrent_streams = NGX_CONF_UNSET_UINT; h2scf->concurrent_pushes = NGX_CONF_UNSET_UINT; - h2scf->max_requests = NGX_CONF_UNSET_UINT; - - h2scf->max_field_size = NGX_CONF_UNSET_SIZE; - h2scf->max_header_size = NGX_CONF_UNSET_SIZE; h2scf->preread_size = NGX_CONF_UNSET_SIZE; h2scf->streams_index_mask = NGX_CONF_UNSET_UINT; - h2scf->recv_timeout = NGX_CONF_UNSET_MSEC; - h2scf->idle_timeout = NGX_CONF_UNSET_MSEC; - #if (T_NGX_HTTP2_SRV_ENABLE) h2scf->enable = NGX_CONF_UNSET; #endif @@ -394,23 +352,12 @@ ngx_http_v2_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) prev->concurrent_streams, 128); ngx_conf_merge_uint_value(conf->concurrent_pushes, prev->concurrent_pushes, 10); - ngx_conf_merge_uint_value(conf->max_requests, prev->max_requests, 1000); - - ngx_conf_merge_size_value(conf->max_field_size, prev->max_field_size, - 4096); - ngx_conf_merge_size_value(conf->max_header_size, prev->max_header_size, - 16384); ngx_conf_merge_size_value(conf->preread_size, prev->preread_size, 65536); ngx_conf_merge_uint_value(conf->streams_index_mask, prev->streams_index_mask, 32 - 1); - ngx_conf_merge_msec_value(conf->recv_timeout, - prev->recv_timeout, 30000); - ngx_conf_merge_msec_value(conf->idle_timeout, - prev->idle_timeout, 180000); - #if (T_NGX_HTTP2_SRV_ENABLE) if (conf->enable == NGX_CONF_UNSET) { conf->enable = prev->enable; @@ -619,11 +566,14 @@ ngx_http_v2_chunk_size(ngx_conf_t *cf, void *post, void *data) static char * -ngx_http_v2_spdy_deprecated(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +ngx_http_v2_obsolete(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { + ngx_conf_deprecated_t *d = cmd->post; + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "invalid directive \"%V\": ngx_http_spdy_module " - "was superseded by ngx_http_v2_module", &cmd->name); + "the \"%s\" directive is obsolete, " + "use the \"%s\" directive instead", + d->old_name, d->new_name); return NGX_CONF_OK; } diff --git a/src/http/v2/ngx_http_v2_module.h b/src/http/v2/ngx_http_v2_module.h index 29c0b198eb..0173937bef 100644 --- a/src/http/v2/ngx_http_v2_module.h +++ b/src/http/v2/ngx_http_v2_module.h @@ -24,13 +24,8 @@ typedef struct { size_t pool_size; ngx_uint_t concurrent_streams; ngx_uint_t concurrent_pushes; - ngx_uint_t max_requests; - size_t max_field_size; - size_t max_header_size; size_t preread_size; ngx_uint_t streams_index_mask; - ngx_msec_t recv_timeout; - ngx_msec_t idle_timeout; #if (T_NGX_HTTP2_SRV_ENABLE) ngx_flag_t enable; #endif diff --git a/src/mail/ngx_mail.c b/src/mail/ngx_mail.c index f17c2ccc32..890d8153a0 100644 --- a/src/mail/ngx_mail.c +++ b/src/mail/ngx_mail.c @@ -405,6 +405,7 @@ ngx_mail_add_addrs(ngx_conf_t *cf, ngx_mail_port_t *mport, #if (NGX_MAIL_SSL) addrs[i].conf.ssl = addr[i].opt.ssl; #endif + addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; addrs[i].conf.addr_text = addr[i].opt.addr_text; } @@ -439,6 +440,7 @@ ngx_mail_add_addrs6(ngx_conf_t *cf, ngx_mail_port_t *mport, #if (NGX_MAIL_SSL) addrs6[i].conf.ssl = addr[i].opt.ssl; #endif + addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; addrs6[i].conf.addr_text = addr[i].opt.addr_text; } diff --git a/src/mail/ngx_mail.h b/src/mail/ngx_mail.h index d904f25f1b..e0c62b7abf 100644 --- a/src/mail/ngx_mail.h +++ b/src/mail/ngx_mail.h @@ -41,6 +41,7 @@ typedef struct { unsigned ipv6only:1; #endif unsigned so_keepalive:2; + unsigned proxy_protocol:1; #if (NGX_HAVE_KEEPALIVE_TUNABLE) int tcp_keepidle; int tcp_keepintvl; @@ -55,7 +56,8 @@ typedef struct { typedef struct { ngx_mail_conf_ctx_t *ctx; ngx_str_t addr_text; - ngx_uint_t ssl; /* unsigned ssl:1; */ + unsigned ssl:1; + unsigned proxy_protocol:1; } ngx_mail_addr_conf_t; typedef struct { @@ -113,6 +115,8 @@ typedef struct { ngx_msec_t timeout; ngx_msec_t resolver_timeout; + ngx_uint_t max_errors; + ngx_str_t server_name; u_char *file_name; @@ -162,10 +166,12 @@ typedef enum { ngx_smtp_auth_external, ngx_smtp_helo, ngx_smtp_helo_xclient, + ngx_smtp_helo_auth, ngx_smtp_helo_from, ngx_smtp_xclient, ngx_smtp_xclient_from, ngx_smtp_xclient_helo, + ngx_smtp_xclient_auth, ngx_smtp_from, ngx_smtp_to } ngx_smtp_state_e; @@ -174,6 +180,7 @@ typedef enum { typedef struct { ngx_peer_connection_t upstream; ngx_buf_t *buffer; + ngx_uint_t proxy_protocol; /* unsigned proxy_protocol:1; */ } ngx_mail_proxy_ctx_t; @@ -195,6 +202,7 @@ typedef struct { ngx_uint_t mail_state; + unsigned ssl:1; unsigned protocol:3; unsigned blocked:1; unsigned quit:1; @@ -225,14 +233,15 @@ typedef struct { ngx_uint_t command; ngx_array_t args; + ngx_uint_t errors; ngx_uint_t login_attempt; /* used to parse POP3/IMAP/SMTP command */ ngx_uint_t state; + u_char *tag_start; u_char *cmd_start; u_char *arg_start; - u_char *arg_end; ngx_uint_t literal_len; } ngx_mail_session_t; @@ -315,6 +324,7 @@ typedef ngx_int_t (*ngx_mail_parse_command_pt)(ngx_mail_session_t *s); struct ngx_mail_protocol_s { ngx_str_t name; + ngx_str_t alpn; in_port_t port[4]; ngx_uint_t type; @@ -403,6 +413,7 @@ char *ngx_mail_capabilities(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); /* STUB */ void ngx_mail_proxy_init(ngx_mail_session_t *s, ngx_addr_t *peer); void ngx_mail_auth_http_init(ngx_mail_session_t *s); +ngx_int_t ngx_mail_realip_handler(ngx_mail_session_t *s); /**/ diff --git a/src/mail/ngx_mail_auth_http_module.c b/src/mail/ngx_mail_auth_http_module.c index 6b57358b47..27f64b92e0 100644 --- a/src/mail/ngx_mail_auth_http_module.c +++ b/src/mail/ngx_mail_auth_http_module.c @@ -1135,10 +1135,10 @@ ngx_mail_auth_http_create_request(ngx_mail_session_t *s, ngx_pool_t *pool, size_t len; ngx_buf_t *b; ngx_str_t login, passwd; -#if (NGX_MAIL_SSL) - ngx_str_t verify, subject, issuer, serial, fingerprint, - raw_cert, cert; ngx_connection_t *c; +#if (NGX_MAIL_SSL) + ngx_str_t protocol, cipher, verify, subject, issuer, + serial, fingerprint, raw_cert, cert; ngx_mail_ssl_conf_t *sslcf; #endif ngx_mail_core_srv_conf_t *cscf; @@ -1151,9 +1151,29 @@ ngx_mail_auth_http_create_request(ngx_mail_session_t *s, ngx_pool_t *pool, return NULL; } + c = s->connection; + #if (NGX_MAIL_SSL) - c = s->connection; + if (c->ssl) { + + if (ngx_ssl_get_protocol(c, pool, &protocol) != NGX_OK) { + return NULL; + } + + protocol.len = ngx_strlen(protocol.data); + + if (ngx_ssl_get_cipher_name(c, pool, &cipher) != NGX_OK) { + return NULL; + } + + cipher.len = ngx_strlen(cipher.data); + + } else { + ngx_str_null(&protocol); + ngx_str_null(&cipher); + } + sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module); if (c->ssl && sslcf->verify) { @@ -1224,22 +1244,53 @@ ngx_mail_auth_http_create_request(ngx_mail_session_t *s, ngx_pool_t *pool, + sizeof("Client-IP: ") - 1 + s->connection->addr_text.len + sizeof(CRLF) - 1 + sizeof("Client-Host: ") - 1 + s->host.len + sizeof(CRLF) - 1 - + sizeof("Auth-SMTP-Helo: ") - 1 + s->smtp_helo.len + sizeof(CRLF) - 1 - + sizeof("Auth-SMTP-From: ") - 1 + s->smtp_from.len + sizeof(CRLF) - 1 - + sizeof("Auth-SMTP-To: ") - 1 + s->smtp_to.len + sizeof(CRLF) - 1 -#if (NGX_MAIL_SSL) - + sizeof("Auth-SSL: on" CRLF) - 1 - + sizeof("Auth-SSL-Verify: ") - 1 + verify.len + sizeof(CRLF) - 1 - + sizeof("Auth-SSL-Subject: ") - 1 + subject.len + sizeof(CRLF) - 1 - + sizeof("Auth-SSL-Issuer: ") - 1 + issuer.len + sizeof(CRLF) - 1 - + sizeof("Auth-SSL-Serial: ") - 1 + serial.len + sizeof(CRLF) - 1 - + sizeof("Auth-SSL-Fingerprint: ") - 1 + fingerprint.len - + sizeof(CRLF) - 1 - + sizeof("Auth-SSL-Cert: ") - 1 + cert.len + sizeof(CRLF) - 1 -#endif + ahcf->header.len + sizeof(CRLF) - 1; + if (c->proxy_protocol) { + len += sizeof("Proxy-Protocol-Addr: ") - 1 + + c->proxy_protocol->src_addr.len + sizeof(CRLF) - 1 + + sizeof("Proxy-Protocol-Port: ") - 1 + + sizeof("65535") - 1 + sizeof(CRLF) - 1 + + sizeof("Proxy-Protocol-Server-Addr: ") - 1 + + c->proxy_protocol->dst_addr.len + sizeof(CRLF) - 1 + + sizeof("Proxy-Protocol-Server-Port: ") - 1 + + sizeof("65535") - 1 + sizeof(CRLF) - 1; + } + + if (s->auth_method == NGX_MAIL_AUTH_NONE) { + len += sizeof("Auth-SMTP-Helo: ") - 1 + s->smtp_helo.len + + sizeof(CRLF) - 1 + + sizeof("Auth-SMTP-From: ") - 1 + s->smtp_from.len + + sizeof(CRLF) - 1 + + sizeof("Auth-SMTP-To: ") - 1 + s->smtp_to.len + + sizeof(CRLF) - 1; + } + +#if (NGX_MAIL_SSL) + + if (c->ssl) { + len += sizeof("Auth-SSL: on" CRLF) - 1 + + sizeof("Auth-SSL-Protocol: ") - 1 + protocol.len + + sizeof(CRLF) - 1 + + sizeof("Auth-SSL-Cipher: ") - 1 + cipher.len + + sizeof(CRLF) - 1 + + sizeof("Auth-SSL-Verify: ") - 1 + verify.len + + sizeof(CRLF) - 1 + + sizeof("Auth-SSL-Subject: ") - 1 + subject.len + + sizeof(CRLF) - 1 + + sizeof("Auth-SSL-Issuer: ") - 1 + issuer.len + + sizeof(CRLF) - 1 + + sizeof("Auth-SSL-Serial: ") - 1 + serial.len + + sizeof(CRLF) - 1 + + sizeof("Auth-SSL-Fingerprint: ") - 1 + fingerprint.len + + sizeof(CRLF) - 1 + + sizeof("Auth-SSL-Cert: ") - 1 + cert.len + + sizeof(CRLF) - 1; + } + +#endif + b = ngx_create_temp_buf(pool, len); if (b == NULL) { return NULL; @@ -1298,6 +1349,26 @@ ngx_mail_auth_http_create_request(ngx_mail_session_t *s, ngx_pool_t *pool, *b->last++ = CR; *b->last++ = LF; } + if (c->proxy_protocol) { + b->last = ngx_cpymem(b->last, "Proxy-Protocol-Addr: ", + sizeof("Proxy-Protocol-Addr: ") - 1); + b->last = ngx_copy(b->last, c->proxy_protocol->src_addr.data, + c->proxy_protocol->src_addr.len); + *b->last++ = CR; *b->last++ = LF; + + b->last = ngx_sprintf(b->last, "Proxy-Protocol-Port: %d" CRLF, + c->proxy_protocol->src_port); + + b->last = ngx_cpymem(b->last, "Proxy-Protocol-Server-Addr: ", + sizeof("Proxy-Protocol-Server-Addr: ") - 1); + b->last = ngx_copy(b->last, c->proxy_protocol->dst_addr.data, + c->proxy_protocol->dst_addr.len); + *b->last++ = CR; *b->last++ = LF; + + b->last = ngx_sprintf(b->last, "Proxy-Protocol-Server-Port: %d" CRLF, + c->proxy_protocol->dst_port); + } + if (s->auth_method == NGX_MAIL_AUTH_NONE) { /* HELO, MAIL FROM, and RCPT TO can't contain CRLF, no need to escape */ @@ -1325,6 +1396,20 @@ ngx_mail_auth_http_create_request(ngx_mail_session_t *s, ngx_pool_t *pool, b->last = ngx_cpymem(b->last, "Auth-SSL: on" CRLF, sizeof("Auth-SSL: on" CRLF) - 1); + if (protocol.len) { + b->last = ngx_cpymem(b->last, "Auth-SSL-Protocol: ", + sizeof("Auth-SSL-Protocol: ") - 1); + b->last = ngx_copy(b->last, protocol.data, protocol.len); + *b->last++ = CR; *b->last++ = LF; + } + + if (cipher.len) { + b->last = ngx_cpymem(b->last, "Auth-SSL-Cipher: ", + sizeof("Auth-SSL-Cipher: ") - 1); + b->last = ngx_copy(b->last, cipher.data, cipher.len); + *b->last++ = CR; *b->last++ = LF; + } + if (verify.len) { b->last = ngx_cpymem(b->last, "Auth-SSL-Verify: ", sizeof("Auth-SSL-Verify: ") - 1); diff --git a/src/mail/ngx_mail_core_module.c b/src/mail/ngx_mail_core_module.c index e16d702380..115671ca4e 100644 --- a/src/mail/ngx_mail_core_module.c +++ b/src/mail/ngx_mail_core_module.c @@ -85,6 +85,13 @@ static ngx_command_t ngx_mail_core_commands[] = { offsetof(ngx_mail_core_srv_conf_t, resolver_timeout), NULL }, + { ngx_string("max_errors"), + NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_MAIL_SRV_CONF_OFFSET, + offsetof(ngx_mail_core_srv_conf_t, max_errors), + NULL }, + ngx_null_command }; @@ -163,6 +170,8 @@ ngx_mail_core_create_srv_conf(ngx_conf_t *cf) cscf->timeout = NGX_CONF_UNSET_MSEC; cscf->resolver_timeout = NGX_CONF_UNSET_MSEC; + cscf->max_errors = NGX_CONF_UNSET_UINT; + cscf->resolver = NGX_CONF_UNSET_PTR; cscf->file_name = cf->conf_file->file.name.data; @@ -182,6 +191,7 @@ ngx_mail_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_msec_value(conf->resolver_timeout, prev->resolver_timeout, 30000); + ngx_conf_merge_uint_value(conf->max_errors, prev->max_errors, 5); ngx_conf_merge_str_value(conf->server_name, prev->server_name, ""); @@ -548,6 +558,11 @@ ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } + if (ngx_strcmp(value[i].data, "proxy_protocol") == 0) { + ls->proxy_protocol = 1; + continue; + } + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the invalid \"%V\" parameter", &value[i]); return NGX_CONF_ERROR; diff --git a/src/mail/ngx_mail_handler.c b/src/mail/ngx_mail_handler.c index 803a247d23..246ba97cf9 100644 --- a/src/mail/ngx_mail_handler.c +++ b/src/mail/ngx_mail_handler.c @@ -11,6 +11,8 @@ #include +static void ngx_mail_proxy_protocol_handler(ngx_event_t *rev); +static void ngx_mail_init_session_handler(ngx_event_t *rev); static void ngx_mail_init_session(ngx_connection_t *c); #if (NGX_MAIL_SSL) @@ -26,6 +28,7 @@ ngx_mail_init_connection(ngx_connection_t *c) { size_t len; ngx_uint_t i; + ngx_event_t *rev; ngx_mail_port_t *port; struct sockaddr *sa; struct sockaddr_in *sin; @@ -129,6 +132,10 @@ ngx_mail_init_connection(ngx_connection_t *c) s->main_conf = addr_conf->ctx->main_conf; s->srv_conf = addr_conf->ctx->srv_conf; +#if (NGX_MAIL_SSL) + s->ssl = addr_conf->ssl; +#endif + s->addr_text = &addr_conf->addr_text; c->data = s; @@ -159,13 +166,126 @@ ngx_mail_init_connection(ngx_connection_t *c) c->log_error = NGX_ERROR_INFO; + rev = c->read; + rev->handler = ngx_mail_init_session_handler; + + if (addr_conf->proxy_protocol) { + c->log->action = "reading PROXY protocol"; + + rev->handler = ngx_mail_proxy_protocol_handler; + + if (!rev->ready) { + ngx_add_timer(rev, cscf->timeout); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_mail_close_connection(c); + } + + return; + } + } + + if (ngx_use_accept_mutex) { + ngx_post_event(rev, &ngx_posted_events); + return; + } + + rev->handler(rev); +} + + +static void +ngx_mail_proxy_protocol_handler(ngx_event_t *rev) +{ + u_char *p, buf[NGX_PROXY_PROTOCOL_MAX_HEADER]; + size_t size; + ssize_t n; + ngx_err_t err; + ngx_connection_t *c; + ngx_mail_session_t *s; + ngx_mail_core_srv_conf_t *cscf; + + c = rev->data; + s = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, + "mail PROXY protocol handler"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + c->timedout = 1; + ngx_mail_close_connection(c); + return; + } + + n = recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK); + + err = ngx_socket_errno; + + ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0, "recv(): %z", n); + + if (n == -1) { + if (err == NGX_EAGAIN) { + rev->ready = 0; + + if (!rev->timer_set) { + cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module); + ngx_add_timer(rev, cscf->timeout); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_mail_close_connection(c); + } + + return; + } + + ngx_connection_error(c, err, "recv() failed"); + + ngx_mail_close_connection(c); + return; + } + + p = ngx_proxy_protocol_read(c, buf, buf + n); + + if (p == NULL) { + ngx_mail_close_connection(c); + return; + } + + size = p - buf; + + if (c->recv(c, buf, size) != (ssize_t) size) { + ngx_mail_close_connection(c); + return; + } + + if (ngx_mail_realip_handler(s) != NGX_OK) { + ngx_mail_close_connection(c); + return; + } + + ngx_mail_init_session_handler(rev); +} + + +static void +ngx_mail_init_session_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + + c = rev->data; + #if (NGX_MAIL_SSL) { + ngx_mail_session_t *s; ngx_mail_ssl_conf_t *sslcf; + s = c->data; + sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module); - if (sslcf->enable || addr_conf->ssl) { + if (sslcf->enable || s->ssl) { c->log->action = "SSL handshaking"; ngx_mail_ssl_init_connection(&sslcf->ssl, c); @@ -215,9 +335,10 @@ ngx_mail_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c) s = c->data; - cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module); - - ngx_add_timer(c->read, cscf->timeout); + if (!c->read->timer_set) { + cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module); + ngx_add_timer(c->read, cscf->timeout); + } c->ssl->handler = ngx_mail_ssl_handshake_handler; @@ -338,6 +459,8 @@ ngx_mail_init_session(ngx_connection_t *c) s = c->data; + c->log->action = "sending client greeting line"; + cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module); s->protocol = cscf->protocol->type; @@ -710,25 +833,23 @@ ngx_mail_read_command(ngx_mail_session_t *s, ngx_connection_t *c) ngx_str_t l; ngx_mail_core_srv_conf_t *cscf; - n = c->recv(c, s->buffer->last, s->buffer->end - s->buffer->last); + if (s->buffer->last < s->buffer->end) { - if (n == NGX_ERROR || n == 0) { - ngx_mail_close_connection(c); - return NGX_ERROR; - } - - if (n > 0) { - s->buffer->last += n; - } + n = c->recv(c, s->buffer->last, s->buffer->end - s->buffer->last); - if (n == NGX_AGAIN) { - if (ngx_handle_read_event(c->read, 0) != NGX_OK) { - ngx_mail_session_internal_server_error(s); + if (n == NGX_ERROR || n == 0) { + ngx_mail_close_connection(c); return NGX_ERROR; } - if (s->buffer->pos == s->buffer->last) { - return NGX_AGAIN; + if (n > 0) { + s->buffer->last += n; + } + + if (n == NGX_AGAIN) { + if (s->buffer->pos == s->buffer->last) { + return NGX_AGAIN; + } } } @@ -753,7 +874,20 @@ ngx_mail_read_command(ngx_mail_session_t *s, ngx_connection_t *c) return NGX_MAIL_PARSE_INVALID_COMMAND; } - if (rc == NGX_IMAP_NEXT || rc == NGX_MAIL_PARSE_INVALID_COMMAND) { + if (rc == NGX_MAIL_PARSE_INVALID_COMMAND) { + + s->errors++; + + if (s->errors >= cscf->max_errors) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent too many invalid commands"); + s->quit = 1; + } + + return rc; + } + + if (rc == NGX_IMAP_NEXT) { return rc; } diff --git a/src/mail/ngx_mail_imap_handler.c b/src/mail/ngx_mail_imap_handler.c index 3bf09ec3c9..291e87a4de 100644 --- a/src/mail/ngx_mail_imap_handler.c +++ b/src/mail/ngx_mail_imap_handler.c @@ -101,10 +101,9 @@ ngx_mail_imap_init_protocol(ngx_event_t *rev) void ngx_mail_imap_auth_state(ngx_event_t *rev) { - u_char *p, *dst, *src, *end; - ngx_str_t *arg; + u_char *p; ngx_int_t rc; - ngx_uint_t tag, i; + ngx_uint_t tag; ngx_connection_t *c; ngx_mail_session_t *s; @@ -123,6 +122,12 @@ ngx_mail_imap_auth_state(ngx_event_t *rev) if (s->out.len) { ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap send handler busy"); s->blocked = 1; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_close_connection(c); + return; + } + return; } @@ -130,7 +135,16 @@ ngx_mail_imap_auth_state(ngx_event_t *rev) rc = ngx_mail_read_command(s, c); - if (rc == NGX_AGAIN || rc == NGX_ERROR) { + if (rc == NGX_AGAIN) { + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_session_internal_server_error(s); + return; + } + + return; + } + + if (rc == NGX_ERROR) { return; } @@ -143,27 +157,6 @@ ngx_mail_imap_auth_state(ngx_event_t *rev) ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap auth command: %i", s->command); - if (s->backslash) { - - arg = s->args.elts; - - for (i = 0; i < s->args.nelts; i++) { - dst = arg[i].data; - end = dst + arg[i].len; - - for (src = dst; src < end; dst++) { - *dst = *src; - if (*src++ == '\\') { - *dst = *src++; - } - } - - arg[i].len = dst - arg[i].data; - } - - s->backslash = 0; - } - switch (s->mail_state) { case ngx_imap_start: @@ -233,6 +226,10 @@ ngx_mail_imap_auth_state(ngx_event_t *rev) ngx_str_set(&s->out, imap_next); } + if (s->buffer->pos < s->buffer->last) { + s->blocked = 1; + } + switch (rc) { case NGX_DONE: @@ -282,17 +279,23 @@ ngx_mail_imap_auth_state(ngx_event_t *rev) if (s->state) { /* preserve tag */ - s->arg_start = s->buffer->start + s->tag.len; - s->buffer->pos = s->arg_start; - s->buffer->last = s->arg_start; + s->arg_start = s->buffer->pos; } else { - s->buffer->pos = s->buffer->start; - s->buffer->last = s->buffer->start; + if (s->buffer->pos == s->buffer->last) { + s->buffer->pos = s->buffer->start; + s->buffer->last = s->buffer->start; + } + s->tag.len = 0; } } + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_session_internal_server_error(s); + return; + } + ngx_mail_send(c->write); } @@ -461,6 +464,8 @@ ngx_mail_imap_starttls(ngx_mail_session_t *s, ngx_connection_t *c) if (c->ssl == NULL) { sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module); if (sslcf->starttls) { + s->buffer->pos = s->buffer->start; + s->buffer->last = s->buffer->start; c->read->handler = ngx_mail_starttls_handler; return NGX_OK; } diff --git a/src/mail/ngx_mail_imap_module.c b/src/mail/ngx_mail_imap_module.c index 1f187fdeeb..02c684cd4b 100644 --- a/src/mail/ngx_mail_imap_module.c +++ b/src/mail/ngx_mail_imap_module.c @@ -46,6 +46,7 @@ static ngx_str_t ngx_mail_imap_auth_methods_names[] = { static ngx_mail_protocol_t ngx_mail_imap_protocol = { ngx_string("imap"), + ngx_string("\x04imap"), { 143, 993, 0, 0 }, NGX_MAIL_IMAP_PROTOCOL, diff --git a/src/mail/ngx_mail_parse.c b/src/mail/ngx_mail_parse.c index 2c2cdffa11..4db1f18d30 100644 --- a/src/mail/ngx_mail_parse.c +++ b/src/mail/ngx_mail_parse.c @@ -21,6 +21,8 @@ ngx_mail_pop3_parse_command(ngx_mail_session_t *s) ngx_str_t *arg; enum { sw_start = 0, + sw_command, + sw_invalid, sw_spaces_before_argument, sw_argument, sw_almost_done @@ -35,8 +37,14 @@ ngx_mail_pop3_parse_command(ngx_mail_session_t *s) /* POP3 command */ case sw_start: + s->cmd_start = p; + state = sw_command; + + /* fall through */ + + case sw_command: if (ch == ' ' || ch == CR || ch == LF) { - c = s->buffer->start; + c = s->cmd_start; if (p - c == 4) { @@ -85,6 +93,9 @@ ngx_mail_pop3_parse_command(ngx_mail_session_t *s) goto invalid; } + s->cmd.data = s->cmd_start; + s->cmd.len = p - s->cmd_start; + switch (ch) { case ' ': state = sw_spaces_before_argument; @@ -104,16 +115,17 @@ ngx_mail_pop3_parse_command(ngx_mail_session_t *s) break; + case sw_invalid: + goto invalid; + case sw_spaces_before_argument: switch (ch) { case ' ': break; case CR: state = sw_almost_done; - s->arg_end = p; break; case LF: - s->arg_end = p; goto done; default: if (s->args.nelts <= 2) { @@ -188,37 +200,39 @@ ngx_mail_pop3_parse_command(ngx_mail_session_t *s) done: s->buffer->pos = p + 1; - - if (s->arg_start) { - arg = ngx_array_push(&s->args); - if (arg == NULL) { - return NGX_ERROR; - } - arg->len = s->arg_end - s->arg_start; - arg->data = s->arg_start; - s->arg_start = NULL; - } - s->state = (s->command != NGX_POP3_AUTH) ? sw_start : sw_argument; return NGX_OK; invalid: - s->state = sw_start; - s->arg_start = NULL; + s->state = sw_invalid; - return NGX_MAIL_PARSE_INVALID_COMMAND; + /* skip invalid command till LF */ + + for ( /* void */ ; p < s->buffer->last; p++) { + if (*p == LF) { + s->state = sw_start; + s->buffer->pos = p + 1; + return NGX_MAIL_PARSE_INVALID_COMMAND; + } + } + + s->buffer->pos = p; + + return NGX_AGAIN; } ngx_int_t ngx_mail_imap_parse_command(ngx_mail_session_t *s) { - u_char ch, *p, *c; + u_char ch, *p, *c, *dst, *src, *end; ngx_str_t *arg; enum { sw_start = 0, + sw_tag, + sw_invalid, sw_spaces_before_command, sw_command, sw_spaces_before_argument, @@ -241,31 +255,45 @@ ngx_mail_imap_parse_command(ngx_mail_session_t *s) /* IMAP tag */ case sw_start: + s->tag_start = p; + state = sw_tag; + + /* fall through */ + + case sw_tag: switch (ch) { case ' ': - s->tag.len = p - s->buffer->start + 1; - s->tag.data = s->buffer->start; + s->tag.len = p - s->tag_start + 1; + s->tag.data = s->tag_start; state = sw_spaces_before_command; break; case CR: - s->state = sw_start; - return NGX_MAIL_PARSE_INVALID_COMMAND; case LF: - s->state = sw_start; - return NGX_MAIL_PARSE_INVALID_COMMAND; + goto invalid; + default: + if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') + && (ch < '0' || ch > '9') && ch != '-' && ch != '.' + && ch != '_') + { + goto invalid; + } + if (p - s->tag_start > 31) { + goto invalid; + } + break; } break; + case sw_invalid: + goto invalid; + case sw_spaces_before_command: switch (ch) { case ' ': break; case CR: - s->state = sw_start; - return NGX_MAIL_PARSE_INVALID_COMMAND; case LF: - s->state = sw_start; - return NGX_MAIL_PARSE_INVALID_COMMAND; + goto invalid; default: s->cmd_start = p; state = sw_command; @@ -385,6 +413,9 @@ ngx_mail_imap_parse_command(ngx_mail_session_t *s) goto invalid; } + s->cmd.data = s->cmd_start; + s->cmd.len = p - s->cmd_start; + switch (ch) { case ' ': state = sw_spaces_before_argument; @@ -410,10 +441,8 @@ ngx_mail_imap_parse_command(ngx_mail_session_t *s) break; case CR: state = sw_almost_done; - s->arg_end = p; break; case LF: - s->arg_end = p; goto done; case '"': if (s->args.nelts <= 2) { @@ -460,6 +489,22 @@ ngx_mail_imap_parse_command(ngx_mail_session_t *s) } arg->len = p - s->arg_start; arg->data = s->arg_start; + + if (s->backslash) { + dst = s->arg_start; + end = p; + + for (src = dst; src < end; dst++) { + *dst = *src; + if (*src++ == '\\') { + *dst = *src++; + } + } + + arg->len = dst - s->arg_start; + s->backslash = 0; + } + s->arg_start = NULL; switch (ch) { @@ -588,34 +633,46 @@ ngx_mail_imap_parse_command(ngx_mail_session_t *s) done: s->buffer->pos = p + 1; - - if (s->arg_start) { - arg = ngx_array_push(&s->args); - if (arg == NULL) { - return NGX_ERROR; - } - arg->len = s->arg_end - s->arg_start; - arg->data = s->arg_start; - - s->arg_start = NULL; - s->cmd_start = NULL; - s->quoted = 0; - s->no_sync_literal = 0; - s->literal_len = 0; - } - s->state = (s->command != NGX_IMAP_AUTHENTICATE) ? sw_start : sw_argument; return NGX_OK; invalid: - s->state = sw_start; + s->state = sw_invalid; s->quoted = 0; + s->backslash = 0; s->no_sync_literal = 0; s->literal_len = 0; - return NGX_MAIL_PARSE_INVALID_COMMAND; + /* skip invalid command till LF */ + + for ( /* void */ ; p < s->buffer->last; p++) { + if (*p == LF) { + s->state = sw_start; + s->buffer->pos = p + 1; + + /* detect non-synchronizing literals */ + + if ((size_t) (p - s->buffer->start) > sizeof("{1+}") - 1) { + p--; + + if (*p == CR) { + p--; + } + + if (*p == '}' && *(p - 1) == '+') { + s->quit = 1; + } + } + + return NGX_MAIL_PARSE_INVALID_COMMAND; + } + } + + s->buffer->pos = p; + + return NGX_AGAIN; } @@ -758,10 +815,8 @@ ngx_mail_smtp_parse_command(ngx_mail_session_t *s) break; case CR: state = sw_almost_done; - s->arg_end = p; break; case LF: - s->arg_end = p; goto done; default: if (s->args.nelts <= 10) { @@ -821,17 +876,6 @@ ngx_mail_smtp_parse_command(ngx_mail_session_t *s) done: s->buffer->pos = p + 1; - - if (s->arg_start) { - arg = ngx_array_push(&s->args); - if (arg == NULL) { - return NGX_ERROR; - } - arg->len = s->arg_end - s->arg_start; - arg->data = s->arg_start; - s->arg_start = NULL; - } - s->state = (s->command != NGX_SMTP_AUTH) ? sw_start : sw_argument; return NGX_OK; @@ -839,21 +883,20 @@ ngx_mail_smtp_parse_command(ngx_mail_session_t *s) invalid: s->state = sw_invalid; - s->arg_start = NULL; /* skip invalid command till LF */ - for (p = s->buffer->pos; p < s->buffer->last; p++) { + for ( /* void */ ; p < s->buffer->last; p++) { if (*p == LF) { s->state = sw_start; - p++; - break; + s->buffer->pos = p + 1; + return NGX_MAIL_PARSE_INVALID_COMMAND; } } s->buffer->pos = p; - return NGX_MAIL_PARSE_INVALID_COMMAND; + return NGX_AGAIN; } diff --git a/src/mail/ngx_mail_pop3_handler.c b/src/mail/ngx_mail_pop3_handler.c index 9310c2750f..226e7419bd 100644 --- a/src/mail/ngx_mail_pop3_handler.c +++ b/src/mail/ngx_mail_pop3_handler.c @@ -138,6 +138,12 @@ ngx_mail_pop3_auth_state(ngx_event_t *rev) if (s->out.len) { ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "pop3 send handler busy"); s->blocked = 1; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_close_connection(c); + return; + } + return; } @@ -145,7 +151,16 @@ ngx_mail_pop3_auth_state(ngx_event_t *rev) rc = ngx_mail_read_command(s, c); - if (rc == NGX_AGAIN || rc == NGX_ERROR) { + if (rc == NGX_AGAIN) { + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_session_internal_server_error(s); + return; + } + + return; + } + + if (rc == NGX_ERROR) { return; } @@ -247,6 +262,10 @@ ngx_mail_pop3_auth_state(ngx_event_t *rev) } } + if (s->buffer->pos < s->buffer->last) { + s->blocked = 1; + } + switch (rc) { case NGX_DONE: @@ -268,11 +287,19 @@ ngx_mail_pop3_auth_state(ngx_event_t *rev) case NGX_OK: s->args.nelts = 0; - s->buffer->pos = s->buffer->start; - s->buffer->last = s->buffer->start; + + if (s->buffer->pos == s->buffer->last) { + s->buffer->pos = s->buffer->start; + s->buffer->last = s->buffer->start; + } if (s->state) { - s->arg_start = s->buffer->start; + s->arg_start = s->buffer->pos; + } + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_session_internal_server_error(s); + return; } ngx_mail_send(c->write); @@ -380,6 +407,8 @@ ngx_mail_pop3_stls(ngx_mail_session_t *s, ngx_connection_t *c) if (c->ssl == NULL) { sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module); if (sslcf->starttls) { + s->buffer->pos = s->buffer->start; + s->buffer->last = s->buffer->start; c->read->handler = ngx_mail_starttls_handler; return NGX_OK; } diff --git a/src/mail/ngx_mail_pop3_module.c b/src/mail/ngx_mail_pop3_module.c index a673070736..a257b5a702 100644 --- a/src/mail/ngx_mail_pop3_module.c +++ b/src/mail/ngx_mail_pop3_module.c @@ -46,6 +46,7 @@ static ngx_str_t ngx_mail_pop3_auth_methods_names[] = { static ngx_mail_protocol_t ngx_mail_pop3_protocol = { ngx_string("pop3"), + ngx_string("\x04pop3"), { 110, 995, 0, 0 }, NGX_MAIL_POP3_PROTOCOL, diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c index 1c86e54cfe..a7ab0776e0 100644 --- a/src/mail/ngx_mail_proxy_module.c +++ b/src/mail/ngx_mail_proxy_module.c @@ -16,6 +16,8 @@ typedef struct { ngx_flag_t enable; ngx_flag_t pass_error_message; ngx_flag_t xclient; + ngx_flag_t smtp_auth; + ngx_flag_t proxy_protocol; size_t buffer_size; ngx_msec_t timeout; } ngx_mail_proxy_conf_t; @@ -25,7 +27,8 @@ static void ngx_mail_proxy_block_read(ngx_event_t *rev); static void ngx_mail_proxy_pop3_handler(ngx_event_t *rev); static void ngx_mail_proxy_imap_handler(ngx_event_t *rev); static void ngx_mail_proxy_smtp_handler(ngx_event_t *rev); -static void ngx_mail_proxy_dummy_handler(ngx_event_t *ev); +static void ngx_mail_proxy_write_handler(ngx_event_t *wev); +static ngx_int_t ngx_mail_proxy_send_proxy_protocol(ngx_mail_session_t *s); static ngx_int_t ngx_mail_proxy_read_response(ngx_mail_session_t *s, ngx_uint_t state); static void ngx_mail_proxy_handler(ngx_event_t *ev); @@ -74,6 +77,20 @@ static ngx_command_t ngx_mail_proxy_commands[] = { offsetof(ngx_mail_proxy_conf_t, xclient), NULL }, + { ngx_string("proxy_smtp_auth"), + NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_MAIL_SRV_CONF_OFFSET, + offsetof(ngx_mail_proxy_conf_t, smtp_auth), + NULL }, + + { ngx_string("proxy_protocol"), + NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_MAIL_SRV_CONF_OFFSET, + offsetof(ngx_mail_proxy_conf_t, proxy_protocol), + NULL }, + ngx_null_command }; @@ -148,7 +165,7 @@ ngx_mail_proxy_init(ngx_mail_session_t *s, ngx_addr_t *peer) p->upstream.connection->pool = s->connection->pool; s->connection->read->handler = ngx_mail_proxy_block_read; - p->upstream.connection->write->handler = ngx_mail_proxy_dummy_handler; + p->upstream.connection->write->handler = ngx_mail_proxy_write_handler; pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module); @@ -159,6 +176,8 @@ ngx_mail_proxy_init(ngx_mail_session_t *s, ngx_addr_t *peer) return; } + s->proxy->proxy_protocol = pcf->proxy_protocol; + s->out.len = 0; switch (s->protocol) { @@ -178,6 +197,12 @@ ngx_mail_proxy_init(ngx_mail_session_t *s, ngx_addr_t *peer) s->mail_state = ngx_smtp_start; break; } + + if (rc == NGX_AGAIN) { + return; + } + + ngx_mail_proxy_write_handler(p->upstream.connection->write); } @@ -222,9 +247,25 @@ ngx_mail_proxy_pop3_handler(ngx_event_t *rev) return; } + if (s->proxy->proxy_protocol) { + ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "mail proxy pop3 busy"); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_proxy_internal_server_error(s); + return; + } + + return; + } + rc = ngx_mail_proxy_read_response(s, 0); if (rc == NGX_AGAIN) { + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_proxy_internal_server_error(s); + return; + } + return; } @@ -286,6 +327,10 @@ ngx_mail_proxy_pop3_handler(ngx_event_t *rev) c->log->action = NULL; ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in"); + if (s->buffer->pos < s->buffer->last) { + ngx_post_event(c->write, &ngx_posted_events); + } + ngx_mail_proxy_handler(s->connection->write); return; @@ -306,6 +351,11 @@ ngx_mail_proxy_pop3_handler(ngx_event_t *rev) return; } + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_proxy_internal_server_error(s); + return; + } + s->proxy->buffer->pos = s->proxy->buffer->start; s->proxy->buffer->last = s->proxy->buffer->start; } @@ -335,9 +385,25 @@ ngx_mail_proxy_imap_handler(ngx_event_t *rev) return; } + if (s->proxy->proxy_protocol) { + ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "mail proxy imap busy"); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_proxy_internal_server_error(s); + return; + } + + return; + } + rc = ngx_mail_proxy_read_response(s, s->mail_state); if (rc == NGX_AGAIN) { + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_proxy_internal_server_error(s); + return; + } + return; } @@ -420,6 +486,10 @@ ngx_mail_proxy_imap_handler(ngx_event_t *rev) c->log->action = NULL; ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in"); + if (s->buffer->pos < s->buffer->last) { + ngx_post_event(c->write, &ngx_posted_events); + } + ngx_mail_proxy_handler(s->connection->write); return; @@ -440,6 +510,11 @@ ngx_mail_proxy_imap_handler(ngx_event_t *rev) return; } + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_proxy_internal_server_error(s); + return; + } + s->proxy->buffer->pos = s->proxy->buffer->start; s->proxy->buffer->last = s->proxy->buffer->start; } @@ -450,7 +525,7 @@ ngx_mail_proxy_smtp_handler(ngx_event_t *rev) { u_char *p; ngx_int_t rc; - ngx_str_t line; + ngx_str_t line, auth, encoded; ngx_buf_t *b; ngx_connection_t *c; ngx_mail_session_t *s; @@ -471,9 +546,25 @@ ngx_mail_proxy_smtp_handler(ngx_event_t *rev) return; } + if (s->proxy->proxy_protocol) { + ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "mail proxy smtp busy"); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_proxy_internal_server_error(s); + return; + } + + return; + } + rc = ngx_mail_proxy_read_response(s, s->mail_state); if (rc == NGX_AGAIN) { + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_proxy_internal_server_error(s); + return; + } + return; } @@ -513,6 +604,9 @@ ngx_mail_proxy_smtp_handler(ngx_event_t *rev) } else if (s->auth_method == NGX_MAIL_AUTH_NONE) { s->mail_state = ngx_smtp_helo_from; + } else if (pcf->smtp_auth) { + s->mail_state = ngx_smtp_helo_auth; + } else { s->mail_state = ngx_smtp_helo; } @@ -552,7 +646,9 @@ ngx_mail_proxy_smtp_handler(ngx_event_t *rev) p = ngx_copy(p, s->connection->addr_text.data, s->connection->addr_text.len); - if (s->login.len) { + pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module); + + if (s->login.len && !pcf->smtp_auth) { p = ngx_cpymem(p, " LOGIN=", sizeof(" LOGIN=") - 1); p = ngx_copy(p, s->login.data, s->login.len); } @@ -570,6 +666,9 @@ ngx_mail_proxy_smtp_handler(ngx_event_t *rev) } else if (s->auth_method == NGX_MAIL_AUTH_NONE) { s->mail_state = ngx_smtp_xclient_from; + } else if (pcf->smtp_auth) { + s->mail_state = ngx_smtp_xclient_auth; + } else { s->mail_state = ngx_smtp_xclient; } @@ -595,8 +694,62 @@ ngx_mail_proxy_smtp_handler(ngx_event_t *rev) &s->smtp_helo) - line.data; - s->mail_state = (s->auth_method == NGX_MAIL_AUTH_NONE) ? - ngx_smtp_helo_from : ngx_smtp_helo; + pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module); + + if (s->auth_method == NGX_MAIL_AUTH_NONE) { + s->mail_state = ngx_smtp_helo_from; + + } else if (pcf->smtp_auth) { + s->mail_state = ngx_smtp_helo_auth; + + } else { + s->mail_state = ngx_smtp_helo; + } + + break; + + case ngx_smtp_helo_auth: + case ngx_smtp_xclient_auth: + ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, + "mail proxy send auth"); + + s->connection->log->action = "sending AUTH to upstream"; + + if (s->passwd.data == NULL) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "no password available"); + ngx_mail_proxy_internal_server_error(s); + return; + } + + auth.len = 1 + s->login.len + 1 + s->passwd.len; + auth.data = ngx_pnalloc(c->pool, auth.len); + if (auth.data == NULL) { + ngx_mail_proxy_internal_server_error(s); + return; + } + + auth.len = ngx_sprintf(auth.data, "%Z%V%Z%V", &s->login, &s->passwd) + - auth.data; + + line.len = sizeof("AUTH PLAIN " CRLF) - 1 + + ngx_base64_encoded_length(auth.len); + + line.data = ngx_pnalloc(c->pool, line.len); + if (line.data == NULL) { + ngx_mail_proxy_internal_server_error(s); + return; + } + + encoded.data = ngx_cpymem(line.data, "AUTH PLAIN ", + sizeof("AUTH PLAIN ") - 1); + + ngx_encode_base64(&encoded, &auth); + + p = encoded.data + encoded.len; + *p++ = CR; *p = LF; + + s->mail_state = ngx_smtp_auth_plain; break; @@ -643,6 +796,7 @@ ngx_mail_proxy_smtp_handler(ngx_event_t *rev) case ngx_smtp_helo: case ngx_smtp_xclient: + case ngx_smtp_auth_plain: case ngx_smtp_to: b = s->proxy->buffer; @@ -667,13 +821,12 @@ ngx_mail_proxy_smtp_handler(ngx_event_t *rev) c->log->action = NULL; ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in"); - if (s->buffer->pos == s->buffer->last) { - ngx_mail_proxy_handler(s->connection->write); - - } else { - ngx_mail_proxy_handler(c->write); + if (s->buffer->pos < s->buffer->last) { + ngx_post_event(c->write, &ngx_posted_events); } + ngx_mail_proxy_handler(s->connection->write); + return; default: @@ -692,28 +845,106 @@ ngx_mail_proxy_smtp_handler(ngx_event_t *rev) return; } + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_proxy_internal_server_error(s); + return; + } + s->proxy->buffer->pos = s->proxy->buffer->start; s->proxy->buffer->last = s->proxy->buffer->start; } static void -ngx_mail_proxy_dummy_handler(ngx_event_t *wev) +ngx_mail_proxy_write_handler(ngx_event_t *wev) { ngx_connection_t *c; ngx_mail_session_t *s; - ngx_log_debug0(NGX_LOG_DEBUG_MAIL, wev->log, 0, "mail proxy dummy handler"); + ngx_log_debug0(NGX_LOG_DEBUG_MAIL, wev->log, 0, "mail proxy write handler"); + + c = wev->data; + s = c->data; + + if (s->proxy->proxy_protocol) { + if (ngx_mail_proxy_send_proxy_protocol(s) != NGX_OK) { + return; + } + + s->proxy->proxy_protocol = 0; + } if (ngx_handle_write_event(wev, 0) != NGX_OK) { - c = wev->data; - s = c->data; + ngx_mail_proxy_internal_server_error(s); + } - ngx_mail_proxy_close_session(s); + if (c->read->ready) { + ngx_post_event(c->read, &ngx_posted_events); } } +static ngx_int_t +ngx_mail_proxy_send_proxy_protocol(ngx_mail_session_t *s) +{ + u_char *p; + ssize_t n, size; + ngx_connection_t *c; + u_char buf[NGX_PROXY_PROTOCOL_MAX_HEADER]; + + s->connection->log->action = "sending PROXY protocol header to upstream"; + + ngx_log_debug0(NGX_LOG_DEBUG_MAIL, s->connection->log, 0, + "mail proxy send PROXY protocol header"); + + p = ngx_proxy_protocol_write(s->connection, buf, + buf + NGX_PROXY_PROTOCOL_MAX_HEADER); + if (p == NULL) { + ngx_mail_proxy_internal_server_error(s); + return NGX_ERROR; + } + + c = s->proxy->upstream.connection; + + size = p - buf; + + n = c->send(c, buf, size); + + if (n == NGX_AGAIN) { + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + ngx_mail_proxy_internal_server_error(s); + return NGX_ERROR; + } + + return NGX_AGAIN; + } + + if (n == NGX_ERROR) { + ngx_mail_proxy_internal_server_error(s); + return NGX_ERROR; + } + + if (n != size) { + + /* + * PROXY protocol specification: + * The sender must always ensure that the header + * is sent at once, so that the transport layer + * maintains atomicity along the path to the receiver. + */ + + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "could not send PROXY protocol header at once"); + + ngx_mail_proxy_internal_server_error(s); + + return NGX_ERROR; + } + + return NGX_OK; +} + + static ngx_int_t ngx_mail_proxy_read_response(ngx_mail_session_t *s, ngx_uint_t state) { @@ -824,6 +1055,7 @@ ngx_mail_proxy_read_response(ngx_mail_session_t *s, ngx_uint_t state) case ngx_smtp_helo: case ngx_smtp_helo_xclient: case ngx_smtp_helo_from: + case ngx_smtp_helo_auth: case ngx_smtp_from: if (p[0] == '2' && p[1] == '5' && p[2] == '0') { return NGX_OK; @@ -833,11 +1065,18 @@ ngx_mail_proxy_read_response(ngx_mail_session_t *s, ngx_uint_t state) case ngx_smtp_xclient: case ngx_smtp_xclient_from: case ngx_smtp_xclient_helo: + case ngx_smtp_xclient_auth: if (p[0] == '2' && (p[1] == '2' || p[1] == '5') && p[2] == '0') { return NGX_OK; } break; + case ngx_smtp_auth_plain: + if (p[0] == '2' && p[1] == '3' && p[2] == '5') { + return NGX_OK; + } + break; + case ngx_smtp_to: return NGX_OK; } @@ -1102,6 +1341,8 @@ ngx_mail_proxy_create_conf(ngx_conf_t *cf) pcf->enable = NGX_CONF_UNSET; pcf->pass_error_message = NGX_CONF_UNSET; pcf->xclient = NGX_CONF_UNSET; + pcf->smtp_auth = NGX_CONF_UNSET; + pcf->proxy_protocol = NGX_CONF_UNSET; pcf->buffer_size = NGX_CONF_UNSET_SIZE; pcf->timeout = NGX_CONF_UNSET_MSEC; @@ -1118,6 +1359,8 @@ ngx_mail_proxy_merge_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->enable, prev->enable, 0); ngx_conf_merge_value(conf->pass_error_message, prev->pass_error_message, 0); ngx_conf_merge_value(conf->xclient, prev->xclient, 1); + ngx_conf_merge_value(conf->smtp_auth, prev->smtp_auth, 0); + ngx_conf_merge_value(conf->proxy_protocol, prev->proxy_protocol, 0); ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, (size_t) ngx_pagesize); ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 24 * 60 * 60000); diff --git a/src/mail/ngx_mail_realip_module.c b/src/mail/ngx_mail_realip_module.c new file mode 100644 index 0000000000..c93d7d33c1 --- /dev/null +++ b/src/mail/ngx_mail_realip_module.c @@ -0,0 +1,269 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + ngx_array_t *from; /* array of ngx_cidr_t */ +} ngx_mail_realip_srv_conf_t; + + +static ngx_int_t ngx_mail_realip_set_addr(ngx_mail_session_t *s, + ngx_addr_t *addr); +static char *ngx_mail_realip_from(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static void *ngx_mail_realip_create_srv_conf(ngx_conf_t *cf); +static char *ngx_mail_realip_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); + + +static ngx_command_t ngx_mail_realip_commands[] = { + + { ngx_string("set_real_ip_from"), + NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1, + ngx_mail_realip_from, + NGX_MAIL_SRV_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_mail_module_t ngx_mail_realip_module_ctx = { + NULL, /* protocol */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_mail_realip_create_srv_conf, /* create server configuration */ + ngx_mail_realip_merge_srv_conf /* merge server configuration */ +}; + + +ngx_module_t ngx_mail_realip_module = { + NGX_MODULE_V1, + &ngx_mail_realip_module_ctx, /* module context */ + ngx_mail_realip_commands, /* module directives */ + NGX_MAIL_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 +}; + + +ngx_int_t +ngx_mail_realip_handler(ngx_mail_session_t *s) +{ + ngx_addr_t addr; + ngx_connection_t *c; + ngx_mail_realip_srv_conf_t *rscf; + + rscf = ngx_mail_get_module_srv_conf(s, ngx_mail_realip_module); + + if (rscf->from == NULL) { + return NGX_OK; + } + + c = s->connection; + + if (c->proxy_protocol == NULL) { + return NGX_OK; + } + + if (ngx_cidr_match(c->sockaddr, rscf->from) != NGX_OK) { + return NGX_OK; + } + + if (ngx_parse_addr(c->pool, &addr, c->proxy_protocol->src_addr.data, + c->proxy_protocol->src_addr.len) + != NGX_OK) + { + return NGX_OK; + } + + ngx_inet_set_port(addr.sockaddr, c->proxy_protocol->src_port); + + return ngx_mail_realip_set_addr(s, &addr); +} + + +static ngx_int_t +ngx_mail_realip_set_addr(ngx_mail_session_t *s, ngx_addr_t *addr) +{ + size_t len; + u_char *p; + u_char text[NGX_SOCKADDR_STRLEN]; + ngx_connection_t *c; + + c = s->connection; + + len = ngx_sock_ntop(addr->sockaddr, addr->socklen, text, + NGX_SOCKADDR_STRLEN, 0); + if (len == 0) { + return NGX_ERROR; + } + + p = ngx_pnalloc(c->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, text, len); + + c->sockaddr = addr->sockaddr; + c->socklen = addr->socklen; + c->addr_text.len = len; + c->addr_text.data = p; + + return NGX_OK; +} + + +static char * +ngx_mail_realip_from(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_mail_realip_srv_conf_t *rscf = conf; + + ngx_int_t rc; + ngx_str_t *value; + ngx_url_t u; + ngx_cidr_t c, *cidr; + ngx_uint_t i; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + value = cf->args->elts; + + if (rscf->from == NULL) { + rscf->from = ngx_array_create(cf->pool, 2, + sizeof(ngx_cidr_t)); + if (rscf->from == NULL) { + return NGX_CONF_ERROR; + } + } + +#if (NGX_HAVE_UNIX_DOMAIN) + + if (ngx_strcmp(value[1].data, "unix:") == 0) { + cidr = ngx_array_push(rscf->from); + if (cidr == NULL) { + return NGX_CONF_ERROR; + } + + cidr->family = AF_UNIX; + return NGX_CONF_OK; + } + +#endif + + rc = ngx_ptocidr(&value[1], &c); + + if (rc != NGX_ERROR) { + if (rc == NGX_DONE) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "low address bits of %V are meaningless", + &value[1]); + } + + cidr = ngx_array_push(rscf->from); + if (cidr == NULL) { + return NGX_CONF_ERROR; + } + + *cidr = c; + + return NGX_CONF_OK; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + u.host = value[1]; + + if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in set_real_ip_from \"%V\"", + u.err, &u.host); + } + + return NGX_CONF_ERROR; + } + + cidr = ngx_array_push_n(rscf->from, u.naddrs); + if (cidr == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(cidr, u.naddrs * sizeof(ngx_cidr_t)); + + for (i = 0; i < u.naddrs; i++) { + cidr[i].family = u.addrs[i].sockaddr->sa_family; + + switch (cidr[i].family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) u.addrs[i].sockaddr; + cidr[i].u.in6.addr = sin6->sin6_addr; + ngx_memset(cidr[i].u.in6.mask.s6_addr, 0xff, 16); + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) u.addrs[i].sockaddr; + cidr[i].u.in.addr = sin->sin_addr.s_addr; + cidr[i].u.in.mask = 0xffffffff; + break; + } + } + + return NGX_CONF_OK; +} + + +static void * +ngx_mail_realip_create_srv_conf(ngx_conf_t *cf) +{ + ngx_mail_realip_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_realip_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->from = NULL; + */ + + return conf; +} + + +static char * +ngx_mail_realip_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_mail_realip_srv_conf_t *prev = parent; + ngx_mail_realip_srv_conf_t *conf = child; + + if (conf->from == NULL) { + conf->from = prev->from; + } + + return NGX_CONF_OK; +} diff --git a/src/mail/ngx_mail_smtp_handler.c b/src/mail/ngx_mail_smtp_handler.c index f1017e0d89..e68ceedfdb 100644 --- a/src/mail/ngx_mail_smtp_handler.c +++ b/src/mail/ngx_mail_smtp_handler.c @@ -449,6 +449,12 @@ ngx_mail_smtp_auth_state(ngx_event_t *rev) if (s->out.len) { ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "smtp send handler busy"); s->blocked = 1; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_close_connection(c); + return; + } + return; } @@ -456,7 +462,16 @@ ngx_mail_smtp_auth_state(ngx_event_t *rev) rc = ngx_mail_read_command(s, c); - if (rc == NGX_AGAIN || rc == NGX_ERROR) { + if (rc == NGX_AGAIN) { + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_session_internal_server_error(s); + return; + } + + return; + } + + if (rc == NGX_ERROR) { return; } @@ -568,6 +583,11 @@ ngx_mail_smtp_auth_state(ngx_event_t *rev) s->arg_start = s->buffer->pos; } + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_mail_session_internal_server_error(s); + return; + } + ngx_mail_send(c->write); } } diff --git a/src/mail/ngx_mail_smtp_module.c b/src/mail/ngx_mail_smtp_module.c index 3b5a2d8f31..0e05fdc034 100644 --- a/src/mail/ngx_mail_smtp_module.c +++ b/src/mail/ngx_mail_smtp_module.c @@ -39,6 +39,7 @@ static ngx_str_t ngx_mail_smtp_auth_methods_names[] = { static ngx_mail_protocol_t ngx_mail_smtp_protocol = { ngx_string("smtp"), + ngx_string("\x04smtp"), { 25, 465, 587, 0 }, NGX_MAIL_SMTP_PROTOCOL, diff --git a/src/mail/ngx_mail_ssl_module.c b/src/mail/ngx_mail_ssl_module.c index e193b298ea..2a1043e661 100644 --- a/src/mail/ngx_mail_ssl_module.c +++ b/src/mail/ngx_mail_ssl_module.c @@ -14,6 +14,12 @@ #define NGX_DEFAULT_ECDH_CURVE "auto" +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation +static int ngx_mail_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, + const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg); +#endif + static void *ngx_mail_ssl_create_conf(ngx_conf_t *cf); static char *ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child); @@ -26,6 +32,9 @@ static char *ngx_mail_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, static char *ngx_mail_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_mail_ssl_conf_command_check(ngx_conf_t *cf, void *post, + void *data); + static ngx_conf_enum_t ngx_mail_starttls_state[] = { { ngx_string("off"), NGX_MAIL_STARTTLS_OFF }, @@ -61,6 +70,10 @@ static ngx_conf_deprecated_t ngx_mail_ssl_deprecated = { }; +static ngx_conf_post_t ngx_mail_ssl_conf_command_post = + { ngx_mail_ssl_conf_command_check }; + + static ngx_command_t ngx_mail_ssl_commands[] = { { ngx_string("ssl"), @@ -196,6 +209,13 @@ static ngx_command_t ngx_mail_ssl_commands[] = { offsetof(ngx_mail_ssl_conf_t, crl), NULL }, + { ngx_string("ssl_conf_command"), + NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE2, + ngx_conf_set_keyval_slot, + NGX_MAIL_SRV_CONF_OFFSET, + offsetof(ngx_mail_ssl_conf_t, conf_commands), + &ngx_mail_ssl_conf_command_post }, + ngx_null_command }; @@ -230,6 +250,54 @@ ngx_module_t ngx_mail_ssl_module = { static ngx_str_t ngx_mail_ssl_sess_id_ctx = ngx_string("MAIL"); +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + +static int +ngx_mail_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, unsigned int inlen, + void *arg) +{ + unsigned int srvlen; + unsigned char *srv; + ngx_connection_t *c; + ngx_mail_session_t *s; + ngx_mail_core_srv_conf_t *cscf; +#if (NGX_DEBUG) + unsigned int i; +#endif + + c = ngx_ssl_get_connection(ssl_conn); + s = c->data; + +#if (NGX_DEBUG) + for (i = 0; i < inlen; i += in[i] + 1) { + ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0, + "SSL ALPN supported by client: %*s", + (size_t) in[i], &in[i + 1]); + } +#endif + + cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module); + + srv = cscf->protocol->alpn.data; + srvlen = cscf->protocol->alpn.len; + + if (SSL_select_next_proto((unsigned char **) out, outlen, srv, srvlen, + in, inlen) + != OPENSSL_NPN_NEGOTIATED) + { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0, + "SSL ALPN selected: %*s", (size_t) *outlen, *out); + + return SSL_TLSEXT_ERR_OK; +} + +#endif + + static void * ngx_mail_ssl_create_conf(ngx_conf_t *cf) { @@ -259,6 +327,7 @@ ngx_mail_ssl_create_conf(ngx_conf_t *cf) scf->certificates = NGX_CONF_UNSET_PTR; scf->certificate_keys = NGX_CONF_UNSET_PTR; scf->passwords = NGX_CONF_UNSET_PTR; + scf->conf_commands = NGX_CONF_UNSET_PTR; scf->prefer_server_ciphers = NGX_CONF_UNSET; scf->verify = NGX_CONF_UNSET_UINT; scf->verify_depth = NGX_CONF_UNSET_UINT; @@ -316,6 +385,8 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS); + ngx_conf_merge_ptr_value(conf->conf_commands, prev->conf_commands, NULL); + conf->ssl.log = cf->log; @@ -377,6 +448,17 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) cln->handler = ngx_ssl_cleanup_ctx; cln->data = &conf->ssl; +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + SSL_CTX_set_alpn_select_cb(conf->ssl.ctx, ngx_mail_ssl_alpn_select, NULL); +#endif + + if (ngx_ssl_ciphers(cf, &conf->ssl, &conf->ciphers, + conf->prefer_server_ciphers) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + if (ngx_ssl_certificates(cf, &conf->ssl, conf->certificates, conf->certificate_keys, conf->passwords) != NGX_OK) @@ -413,13 +495,6 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) } } - if (ngx_ssl_ciphers(cf, &conf->ssl, &conf->ciphers, - conf->prefer_server_ciphers) - != NGX_OK) - { - return NGX_CONF_ERROR; - } - if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) { return NGX_CONF_ERROR; } @@ -461,6 +536,10 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } + if (ngx_ssl_conf_commands(cf, &conf->ssl, conf->conf_commands) != NGX_OK) { + return NGX_CONF_ERROR; + } + return NGX_CONF_OK; } @@ -654,3 +733,14 @@ ngx_mail_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } + + +static char * +ngx_mail_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) +{ +#ifndef SSL_CONF_FLAG_FILE + return "is not supported on this platform"; +#else + return NGX_CONF_OK; +#endif +} diff --git a/src/mail/ngx_mail_ssl_module.h b/src/mail/ngx_mail_ssl_module.h index d6b0b8e0d2..a0a6113173 100644 --- a/src/mail/ngx_mail_ssl_module.h +++ b/src/mail/ngx_mail_ssl_module.h @@ -48,6 +48,7 @@ typedef struct { ngx_str_t ciphers; ngx_array_t *passwords; + ngx_array_t *conf_commands; ngx_shm_zone_t *shm_zone; diff --git a/src/misc/ngx_cpp_test_module.cpp b/src/misc/ngx_cpp_test_module.cpp index 5d2f08d396..002640990f 100644 --- a/src/misc/ngx_cpp_test_module.cpp +++ b/src/misc/ngx_cpp_test_module.cpp @@ -14,6 +14,8 @@ extern "C" { #include #include #include + + #include } // nginx header files should go before other, because they define 64-bit off_t diff --git a/src/os/unix/ngx_atomic.h b/src/os/unix/ngx_atomic.h index 74b8b7f842..fcab2d6a0c 100644 --- a/src/os/unix/ngx_atomic.h +++ b/src/os/unix/ngx_atomic.h @@ -38,6 +38,39 @@ typedef volatile ngx_atomic_uint_t ngx_atomic_t; #define ngx_cpu_pause() +#elif (NGX_HAVE_GCC_ATOMIC) + +/* GCC 4.1 builtin atomic operations */ + +#define NGX_HAVE_ATOMIC_OPS 1 + +typedef long ngx_atomic_int_t; +typedef unsigned long ngx_atomic_uint_t; + +#if (NGX_PTR_SIZE == 8) +#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1) +#else +#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1) +#endif + +typedef volatile ngx_atomic_uint_t ngx_atomic_t; + + +#define ngx_atomic_cmp_set(lock, old, set) \ + __sync_bool_compare_and_swap(lock, old, set) + +#define ngx_atomic_fetch_add(value, add) \ + __sync_fetch_and_add(value, add) + +#define ngx_memory_barrier() __sync_synchronize() + +#if ( __i386__ || __i386 || __amd64__ || __amd64 ) +#define ngx_cpu_pause() __asm__ ("pause") +#else +#define ngx_cpu_pause() +#endif + + #elif (NGX_DARWIN_ATOMIC) /* @@ -88,39 +121,6 @@ typedef uint32_t ngx_atomic_uint_t; typedef volatile ngx_atomic_uint_t ngx_atomic_t; -#elif (NGX_HAVE_GCC_ATOMIC) - -/* GCC 4.1 builtin atomic operations */ - -#define NGX_HAVE_ATOMIC_OPS 1 - -typedef long ngx_atomic_int_t; -typedef unsigned long ngx_atomic_uint_t; - -#if (NGX_PTR_SIZE == 8) -#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1) -#else -#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1) -#endif - -typedef volatile ngx_atomic_uint_t ngx_atomic_t; - - -#define ngx_atomic_cmp_set(lock, old, set) \ - __sync_bool_compare_and_swap(lock, old, set) - -#define ngx_atomic_fetch_add(value, add) \ - __sync_fetch_and_add(value, add) - -#define ngx_memory_barrier() __sync_synchronize() - -#if ( __i386__ || __i386 || __amd64__ || __amd64 ) -#define ngx_cpu_pause() __asm__ ("pause") -#else -#define ngx_cpu_pause() -#endif - - #elif ( __i386__ || __i386 ) typedef int32_t ngx_atomic_int_t; diff --git a/src/os/unix/ngx_errno.c b/src/os/unix/ngx_errno.c index e787b2377e..ca23b2d3f2 100644 --- a/src/os/unix/ngx_errno.c +++ b/src/os/unix/ngx_errno.c @@ -9,6 +9,49 @@ #include +static ngx_str_t ngx_unknown_error = ngx_string("Unknown error"); + + +#if (NGX_HAVE_STRERRORDESC_NP) + +/* + * The strerrordesc_np() function, introduced in glibc 2.32, is + * async-signal-safe. This makes it possible to use it directly, + * without copying error messages. + */ + + +u_char * +ngx_strerror(ngx_err_t err, u_char *errstr, size_t size) +{ + size_t len; + const char *msg; + + msg = strerrordesc_np(err); + + if (msg == NULL) { + msg = (char *) ngx_unknown_error.data; + len = ngx_unknown_error.len; + + } else { + len = ngx_strlen(msg); + } + + size = ngx_min(size, len); + + return ngx_cpymem(errstr, msg, size); +} + + +ngx_int_t +ngx_strerror_init(void) +{ + return NGX_OK; +} + + +#else + /* * The strerror() messages are copied because: * @@ -26,7 +69,8 @@ static ngx_str_t *ngx_sys_errlist; -static ngx_str_t ngx_unknown_error = ngx_string("Unknown error"); +static ngx_err_t ngx_first_error; +static ngx_err_t ngx_last_error; u_char * @@ -34,8 +78,13 @@ ngx_strerror(ngx_err_t err, u_char *errstr, size_t size) { ngx_str_t *msg; - msg = ((ngx_uint_t) err < NGX_SYS_NERR) ? &ngx_sys_errlist[err]: - &ngx_unknown_error; + if (err >= ngx_first_error && err < ngx_last_error) { + msg = &ngx_sys_errlist[err - ngx_first_error]; + + } else { + msg = &ngx_unknown_error; + } + size = ngx_min(size, msg->len); return ngx_cpymem(errstr, msg->data, size); @@ -50,20 +99,92 @@ ngx_strerror_init(void) size_t len; ngx_err_t err; +#if (NGX_SYS_NERR) + ngx_first_error = 0; + ngx_last_error = NGX_SYS_NERR; + +#elif (EPERM > 1000 && EPERM < 0x7fffffff - 1000) + + /* + * If number of errors is not known, and EPERM error code has large + * but reasonable value, guess possible error codes based on the error + * messages returned by strerror(), starting from EPERM. Notably, + * this covers GNU/Hurd, where errors start at 0x40000001. + */ + + for (err = EPERM; err > EPERM - 1000; err--) { + ngx_set_errno(0); + msg = strerror(err); + + if (errno == EINVAL + || msg == NULL + || strncmp(msg, "Unknown error", 13) == 0) + { + continue; + } + + ngx_first_error = err; + } + + for (err = EPERM; err < EPERM + 1000; err++) { + ngx_set_errno(0); + msg = strerror(err); + + if (errno == EINVAL + || msg == NULL + || strncmp(msg, "Unknown error", 13) == 0) + { + continue; + } + + ngx_last_error = err + 1; + } + +#else + + /* + * If number of errors is not known, guess it based on the error + * messages returned by strerror(). + */ + + ngx_first_error = 0; + + for (err = 0; err < 1000; err++) { + ngx_set_errno(0); + msg = strerror(err); + + if (errno == EINVAL + || msg == NULL + || strncmp(msg, "Unknown error", 13) == 0) + { + continue; + } + + ngx_last_error = err + 1; + } + +#endif + /* * ngx_strerror() is not ready to work at this stage, therefore, * malloc() is used and possible errors are logged using strerror(). */ - len = NGX_SYS_NERR * sizeof(ngx_str_t); + len = (ngx_last_error - ngx_first_error) * sizeof(ngx_str_t); ngx_sys_errlist = malloc(len); if (ngx_sys_errlist == NULL) { goto failed; } - for (err = 0; err < NGX_SYS_NERR; err++) { + for (err = ngx_first_error; err < ngx_last_error; err++) { msg = strerror(err); + + if (msg == NULL) { + ngx_sys_errlist[err - ngx_first_error] = ngx_unknown_error; + continue; + } + len = ngx_strlen(msg); p = malloc(len); @@ -72,8 +193,8 @@ ngx_strerror_init(void) } ngx_memcpy(p, msg, len); - ngx_sys_errlist[err].len = len; - ngx_sys_errlist[err].data = p; + ngx_sys_errlist[err - ngx_first_error].len = len; + ngx_sys_errlist[err - ngx_first_error].data = p; } return NGX_OK; @@ -85,3 +206,5 @@ ngx_strerror_init(void) return NGX_ERROR; } + +#endif diff --git a/src/os/unix/ngx_files.c b/src/os/unix/ngx_files.c index 482d327637..1c82a8ead0 100644 --- a/src/os/unix/ngx_files.c +++ b/src/os/unix/ngx_files.c @@ -875,9 +875,28 @@ ngx_fs_bsize(u_char *name) return 512; } +#if (NGX_LINUX) + if ((size_t) fs.f_bsize > ngx_pagesize) { + return 512; + } +#endif + return (size_t) fs.f_bsize; } + +off_t +ngx_fs_available(u_char *name) +{ + struct statfs fs; + + if (statfs((char *) name, &fs) == -1) { + return NGX_MAX_OFF_T_VALUE; + } + + return (off_t) fs.f_bavail * fs.f_bsize; +} + #elif (NGX_HAVE_STATVFS) size_t @@ -893,9 +912,28 @@ ngx_fs_bsize(u_char *name) return 512; } +#if (NGX_LINUX) + if ((size_t) fs.f_frsize > ngx_pagesize) { + return 512; + } +#endif + return (size_t) fs.f_frsize; } + +off_t +ngx_fs_available(u_char *name) +{ + struct statvfs fs; + + if (statvfs((char *) name, &fs) == -1) { + return NGX_MAX_OFF_T_VALUE; + } + + return (off_t) fs.f_bavail * fs.f_frsize; +} + #else size_t @@ -904,4 +942,11 @@ ngx_fs_bsize(u_char *name) return 512; } + +off_t +ngx_fs_available(u_char *name) +{ + return NGX_MAX_OFF_T_VALUE; +} + #endif diff --git a/src/os/unix/ngx_files.h b/src/os/unix/ngx_files.h index 383e38e65c..d084713b6e 100644 --- a/src/os/unix/ngx_files.h +++ b/src/os/unix/ngx_files.h @@ -185,7 +185,10 @@ ngx_int_t ngx_set_file_time(u_char *name, ngx_fd_t fd, time_t s); #define ngx_is_exec(sb) (((sb)->st_mode & S_IXUSR) == S_IXUSR) #define ngx_file_access(sb) ((sb)->st_mode & 0777) #define ngx_file_size(sb) (sb)->st_size -#define ngx_file_fs_size(sb) ngx_max((sb)->st_size, (sb)->st_blocks * 512) +#define ngx_file_fs_size(sb) \ + (((sb)->st_blocks * 512 > (sb)->st_size \ + && (sb)->st_blocks * 512 < (sb)->st_size + 8 * (sb)->st_blksize) \ + ? (sb)->st_blocks * 512 : (sb)->st_size) #define ngx_file_mtime(sb) (sb)->st_mtime #define ngx_file_uniq(sb) (sb)->st_ino @@ -346,6 +349,7 @@ ngx_int_t ngx_directio_off(ngx_fd_t fd); #endif size_t ngx_fs_bsize(u_char *name); +off_t ngx_fs_available(u_char *name); #if (NGX_HAVE_OPENAT) diff --git a/src/os/unix/ngx_freebsd_sendfile_chain.c b/src/os/unix/ngx_freebsd_sendfile_chain.c index 3d415bd2cf..5c6a830d87 100644 --- a/src/os/unix/ngx_freebsd_sendfile_chain.c +++ b/src/os/unix/ngx_freebsd_sendfile_chain.c @@ -32,23 +32,22 @@ ngx_chain_t * ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) { - int rc, flags; - off_t send, prev_send, sent; - size_t file_size; - ssize_t n; - ngx_uint_t eintr, eagain; - ngx_err_t err; - ngx_buf_t *file; - ngx_event_t *wev; - ngx_chain_t *cl; - ngx_iovec_t header, trailer; - struct sf_hdtr hdtr; - struct iovec headers[NGX_IOVS_PREALLOCATE]; - struct iovec trailers[NGX_IOVS_PREALLOCATE]; -#if (NGX_HAVE_AIO_SENDFILE) - ngx_uint_t ebusy; - ngx_event_aio_t *aio; + int rc, flags; + off_t send, prev_send, sent; + size_t file_size; + ssize_t n; + ngx_err_t err; + ngx_buf_t *file; + ngx_uint_t eintr, eagain; +#if (NGX_HAVE_SENDFILE_NODISKIO) + ngx_uint_t ebusy; #endif + ngx_event_t *wev; + ngx_chain_t *cl; + ngx_iovec_t header, trailer; + struct sf_hdtr hdtr; + struct iovec headers[NGX_IOVS_PREALLOCATE]; + struct iovec trailers[NGX_IOVS_PREALLOCATE]; wev = c->write; @@ -77,11 +76,6 @@ ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) eagain = 0; flags = 0; -#if (NGX_HAVE_AIO_SENDFILE && NGX_SUPPRESS_WARN) - aio = NULL; - file = NULL; -#endif - header.iovs = headers; header.nalloc = NGX_IOVS_PREALLOCATE; @@ -90,7 +84,7 @@ ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) for ( ;; ) { eintr = 0; -#if (NGX_HAVE_AIO_SENDFILE) +#if (NGX_HAVE_SENDFILE_NODISKIO) ebusy = 0; #endif prev_send = send; @@ -179,9 +173,14 @@ ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) sent = 0; -#if (NGX_HAVE_AIO_SENDFILE) - aio = file->file->aio; - flags = (aio && aio->preload_handler) ? SF_NODISKIO : 0; +#if (NGX_HAVE_SENDFILE_NODISKIO) + + flags = (c->busy_count <= 2) ? SF_NODISKIO : 0; + + if (file->file->directio) { + flags |= SF_NOCACHE; + } + #endif rc = sendfile(file->file->fd, c->fd, file->file_pos, @@ -199,7 +198,7 @@ ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) eintr = 1; break; -#if (NGX_HAVE_AIO_SENDFILE) +#if (NGX_HAVE_SENDFILE_NODISKIO) case NGX_EBUSY: ebusy = 1; break; @@ -252,54 +251,30 @@ ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) in = ngx_chain_update_sent(in, sent); -#if (NGX_HAVE_AIO_SENDFILE) +#if (NGX_HAVE_SENDFILE_NODISKIO) if (ebusy) { - if (aio->event.active) { - /* - * tolerate duplicate calls; they can happen due to subrequests - * or multiple calls of the next body filter from a filter - */ - - if (sent) { - c->busy_count = 0; - } - - return in; - } - if (sent == 0) { c->busy_count++; - if (c->busy_count > 2) { - ngx_log_error(NGX_LOG_ALERT, c->log, 0, - "sendfile(%V) returned busy again", - &file->file->name); - - c->busy_count = 0; - aio->preload_handler = NULL; - - send = prev_send; - continue; - } + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "sendfile() busy, count:%d", c->busy_count); } else { c->busy_count = 0; } - n = aio->preload_handler(file); - - if (n > 0) { - send = prev_send + sent; - continue; + if (wev->posted) { + ngx_delete_posted_event(wev); } + ngx_post_event(wev, &ngx_posted_next_events); + + wev->ready = 0; return in; } - if (flags == SF_NODISKIO) { - c->busy_count = 0; - } + c->busy_count = 0; #endif diff --git a/src/os/unix/ngx_linux_sendfile_chain.c b/src/os/unix/ngx_linux_sendfile_chain.c index 5695839b0a..101d91a9a8 100644 --- a/src/os/unix/ngx_linux_sendfile_chain.c +++ b/src/os/unix/ngx_linux_sendfile_chain.c @@ -38,6 +38,9 @@ static void ngx_linux_sendfile_thread_handler(void *data, ngx_log_t *log); * On Linux up to 2.6.16 sendfile() does not allow to pass the count parameter * more than 2G-1 bytes even on 64-bit platforms: it returns EINVAL, * so we limit it to 2G-1 bytes. + * + * On Linux 2.6.16 and later, sendfile() silently limits the count parameter + * to 2G minus the page size, even on 64-bit platforms. */ #define NGX_SENDFILE_MAXSIZE 2147483647L @@ -216,7 +219,6 @@ ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) */ send = prev_send + sent; - continue; } if (send >= limit || in == NULL) { @@ -377,15 +379,6 @@ ngx_linux_sendfile_thread(ngx_connection_t *c, ngx_buf_t *file, size_t size) return ctx->sent; } - if (task->event.active && ctx->file == file) { - /* - * tolerate duplicate calls; they can happen due to subrequests - * or multiple calls of the next body filter from a filter - */ - - return NGX_DONE; - } - ctx->file = file; ctx->socket = c->fd; ctx->size = size; diff --git a/src/os/unix/ngx_process_cycle.c b/src/os/unix/ngx_process_cycle.c index 3da8842175..d35d85d450 100644 --- a/src/os/unix/ngx_process_cycle.c +++ b/src/os/unix/ngx_process_cycle.c @@ -19,7 +19,7 @@ static void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type); static void ngx_start_cache_manager_processes(ngx_cycle_t *cycle, ngx_uint_t respawn); -static void ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch); +static void ngx_pass_open_channel(ngx_cycle_t *cycle); static void ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo); static ngx_uint_t ngx_reap_children(ngx_cycle_t *cycle); static void ngx_master_process_exit(ngx_cycle_t *cycle); @@ -81,7 +81,7 @@ ngx_master_process_cycle(ngx_cycle_t *cycle) u_char *p; size_t size; ngx_int_t i; - ngx_uint_t n, sigio; + ngx_uint_t sigio; #if (T_PIPES) ngx_uint_t close_old_pipe; #endif @@ -89,7 +89,6 @@ ngx_master_process_cycle(ngx_cycle_t *cycle) struct itimerval itv; ngx_uint_t live; ngx_msec_t delay; - ngx_listening_t *ls; ngx_core_conf_t *ccf; sigemptyset(&set); @@ -224,16 +223,7 @@ ngx_master_process_cycle(ngx_cycle_t *cycle) if (ngx_quit) { ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_SHUTDOWN_SIGNAL)); - - ls = cycle->listening.elts; - for (n = 0; n < cycle->listening.nelts; n++) { - if (ngx_close_socket(ls[n].fd) == -1) { - ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, - ngx_close_socket_n " %V failed", - &ls[n].addr_text); - } - } - cycle->listening.nelts = 0; + ngx_close_listening_sockets(cycle); continue; } @@ -381,25 +371,16 @@ ngx_single_process_cycle(ngx_cycle_t *cycle) static void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type) { - ngx_int_t i; - ngx_channel_t ch; + ngx_int_t i; ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes"); - ngx_memzero(&ch, sizeof(ngx_channel_t)); - - ch.command = NGX_CMD_OPEN_CHANNEL; - for (i = 0; i < n; i++) { ngx_spawn_process(cycle, ngx_worker_process_cycle, (void *) (intptr_t) i, "worker process", type); - ch.pid = ngx_processes[ngx_process_slot].pid; - ch.slot = ngx_process_slot; - ch.fd = ngx_processes[ngx_process_slot].channel[0]; - - ngx_pass_open_channel(cycle, &ch); + ngx_pass_open_channel(cycle); } } @@ -407,9 +388,8 @@ ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type) static void ngx_start_cache_manager_processes(ngx_cycle_t *cycle, ngx_uint_t respawn) { - ngx_uint_t i, manager, loader; - ngx_path_t **path; - ngx_channel_t ch; + ngx_uint_t i, manager, loader; + ngx_path_t **path; manager = 0; loader = 0; @@ -434,14 +414,7 @@ ngx_start_cache_manager_processes(ngx_cycle_t *cycle, ngx_uint_t respawn) &ngx_cache_manager_ctx, "cache manager process", respawn ? NGX_PROCESS_JUST_RESPAWN : NGX_PROCESS_RESPAWN); - ngx_memzero(&ch, sizeof(ngx_channel_t)); - - ch.command = NGX_CMD_OPEN_CHANNEL; - ch.pid = ngx_processes[ngx_process_slot].pid; - ch.slot = ngx_process_slot; - ch.fd = ngx_processes[ngx_process_slot].channel[0]; - - ngx_pass_open_channel(cycle, &ch); + ngx_pass_open_channel(cycle); if (loader == 0) { return; @@ -451,19 +424,22 @@ ngx_start_cache_manager_processes(ngx_cycle_t *cycle, ngx_uint_t respawn) &ngx_cache_loader_ctx, "cache loader process", respawn ? NGX_PROCESS_JUST_SPAWN : NGX_PROCESS_NORESPAWN); - ch.command = NGX_CMD_OPEN_CHANNEL; - ch.pid = ngx_processes[ngx_process_slot].pid; - ch.slot = ngx_process_slot; - ch.fd = ngx_processes[ngx_process_slot].channel[0]; - - ngx_pass_open_channel(cycle, &ch); + ngx_pass_open_channel(cycle); } static void -ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch) +ngx_pass_open_channel(ngx_cycle_t *cycle) { - ngx_int_t i; + ngx_int_t i; + ngx_channel_t ch; + + ngx_memzero(&ch, sizeof(ngx_channel_t)); + + ch.command = NGX_CMD_OPEN_CHANNEL; + ch.pid = ngx_processes[ngx_process_slot].pid; + ch.slot = ngx_process_slot; + ch.fd = ngx_processes[ngx_process_slot].channel[0]; for (i = 0; i < ngx_last_process; i++) { @@ -476,14 +452,14 @@ ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch) ngx_log_debug6(NGX_LOG_DEBUG_CORE, cycle->log, 0, "pass channel s:%i pid:%P fd:%d to s:%i pid:%P fd:%d", - ch->slot, ch->pid, ch->fd, + ch.slot, ch.pid, ch.fd, i, ngx_processes[i].pid, ngx_processes[i].channel[0]); /* TODO: NGX_AGAIN */ ngx_write_channel(ngx_processes[i].channel[0], - ch, sizeof(ngx_channel_t), cycle->log); + &ch, sizeof(ngx_channel_t), cycle->log); } } @@ -674,13 +650,7 @@ ngx_reap_children(ngx_cycle_t *cycle) continue; } - - ch.command = NGX_CMD_OPEN_CHANNEL; - ch.pid = ngx_processes[ngx_process_slot].pid; - ch.slot = ngx_process_slot; - ch.fd = ngx_processes[ngx_process_slot].channel[0]; - - ngx_pass_open_channel(cycle, &ch); + ngx_pass_open_channel(cycle); #if (T_PIPES) live |= 1; #else diff --git a/src/os/unix/ngx_readv_chain.c b/src/os/unix/ngx_readv_chain.c index a3577ce192..b1ae4b51d2 100644 --- a/src/os/unix/ngx_readv_chain.c +++ b/src/os/unix/ngx_readv_chain.c @@ -96,7 +96,7 @@ ngx_readv_chain(ngx_connection_t *c, ngx_chain_t *chain, off_t limit) iov->iov_len += n; } else { - if (vec.nelts >= IOV_MAX) { + if (vec.nelts == vec.nalloc) { break; } diff --git a/src/os/unix/ngx_udp_sendmsg_chain.c b/src/os/unix/ngx_udp_sendmsg_chain.c index 5399c7916a..3d1d6dde4c 100644 --- a/src/os/unix/ngx_udp_sendmsg_chain.c +++ b/src/os/unix/ngx_udp_sendmsg_chain.c @@ -189,6 +189,13 @@ ngx_udp_output_chain_to_iovec(ngx_iovec_t *vec, ngx_chain_t *in, ngx_log_t *log) return cl; } + /* zero-sized datagram; pretend to have at least 1 iov */ + if (n == 0) { + iov = &vec->iovs[n++]; + iov->iov_base = NULL; + iov->iov_len = 0; + } + vec->count = n; vec->size = total; diff --git a/src/stream/ngx_stream.c b/src/stream/ngx_stream.c index c0978515fb..3fe0e0af79 100755 --- a/src/stream/ngx_stream.c +++ b/src/stream/ngx_stream.c @@ -731,6 +731,10 @@ ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports) ls->ipv6only = addr[i].opt.ipv6only; #endif +#if (NGX_HAVE_TCP_FASTOPEN) + ls->fastopen = addr[i].opt.fastopen; +#endif + #if (NGX_HAVE_REUSEPORT) ls->reuseport = addr[i].opt.reuseport; #endif diff --git a/src/stream/ngx_stream.h b/src/stream/ngx_stream.h index 2d1e1d74d8..e430fa76e7 100755 --- a/src/stream/ngx_stream.h +++ b/src/stream/ngx_stream.h @@ -65,6 +65,9 @@ typedef struct { int backlog; int rcvbuf; int sndbuf; +#if (NGX_HAVE_TCP_FASTOPEN) + int fastopen; +#endif int type; #if (T_NGX_STREAM_SNI) unsigned default_server; diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c index 616757b28a..e485ca5051 100755 --- a/src/stream/ngx_stream_core_module.c +++ b/src/stream/ngx_stream_core_module.c @@ -690,6 +690,10 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ls->type = SOCK_STREAM; ls->ctx = cf->ctx; +#if (NGX_HAVE_TCP_FASTOPEN) + ls->fastopen = -1; +#endif + #if (NGX_HAVE_INET6) ls->ipv6only = 1; #endif @@ -719,6 +723,22 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) continue; } + +#if (NGX_HAVE_TCP_FASTOPEN) + if (ngx_strncmp(value[i].data, "fastopen=", 9) == 0) { + ls->fastopen = ngx_atoi(value[i].data + 9, value[i].len - 9); + ls->bind = 1; + + if (ls->fastopen == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid fastopen \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + continue; + } +#endif + if (ngx_strncmp(value[i].data, "backlog=", 8) == 0) { ls->backlog = ngx_atoi(value[i].data + 8, value[i].len - 8); ls->bind = 1; @@ -945,6 +965,12 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (ls->proxy_protocol) { return "\"proxy_protocol\" parameter is incompatible with \"udp\""; } + +#if (NGX_HAVE_TCP_FASTOPEN) + if (ls->fastopen != -1) { + return "\"fastopen\" parameter is incompatible with \"udp\""; + } +#endif } als = cmcf->listen.elts; diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c index 807cf9a2a7..e6ea205bd4 100644 --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -31,6 +31,7 @@ typedef struct { ngx_uint_t next_upstream_tries; ngx_flag_t next_upstream; ngx_flag_t proxy_protocol; + ngx_flag_t half_close; ngx_stream_upstream_local_t *local; ngx_flag_t socket_keepalive; @@ -46,9 +47,10 @@ typedef struct { 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_stream_complex_value_t *ssl_certificate; + ngx_stream_complex_value_t *ssl_certificate_key; ngx_array_t *ssl_passwords; + ngx_array_t *ssl_conf_commands; ngx_ssl_t *ssl; @@ -103,10 +105,13 @@ static char *ngx_stream_proxy_bind(ngx_conf_t *cf, ngx_command_t *cmd, static ngx_int_t ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s); static char *ngx_stream_proxy_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_stream_proxy_ssl_conf_command_check(ngx_conf_t *cf, void *post, + void *data); static void ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s); static void ngx_stream_proxy_ssl_handshake(ngx_connection_t *pc); static void ngx_stream_proxy_ssl_save_session(ngx_connection_t *c); static ngx_int_t ngx_stream_proxy_ssl_name(ngx_stream_session_t *s); +static ngx_int_t ngx_stream_proxy_ssl_certificate(ngx_stream_session_t *s); static ngx_int_t ngx_stream_proxy_set_ssl(ngx_conf_t *cf, ngx_stream_proxy_srv_conf_t *pscf); @@ -126,6 +131,9 @@ static ngx_conf_bitmask_t ngx_stream_proxy_ssl_protocols[] = { { ngx_null_string, 0 } }; +static ngx_conf_post_t ngx_stream_proxy_ssl_conf_command_post = + { ngx_stream_proxy_ssl_conf_command_check }; + #endif @@ -252,6 +260,13 @@ static ngx_command_t ngx_stream_proxy_commands[] = { offsetof(ngx_stream_proxy_srv_conf_t, proxy_protocol), NULL }, + { ngx_string("proxy_half_close"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, half_close), + NULL }, + #if (NGX_STREAM_SSL) { ngx_string("proxy_ssl"), @@ -326,14 +341,14 @@ static ngx_command_t ngx_stream_proxy_commands[] = { { ngx_string("proxy_ssl_certificate"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_str_slot, + ngx_stream_set_complex_value_zero_slot, NGX_STREAM_SRV_CONF_OFFSET, offsetof(ngx_stream_proxy_srv_conf_t, ssl_certificate), NULL }, { ngx_string("proxy_ssl_certificate_key"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_str_slot, + ngx_stream_set_complex_value_zero_slot, NGX_STREAM_SRV_CONF_OFFSET, offsetof(ngx_stream_proxy_srv_conf_t, ssl_certificate_key), NULL }, @@ -345,6 +360,13 @@ static ngx_command_t ngx_stream_proxy_commands[] = { 0, NULL }, + { ngx_string("proxy_ssl_conf_command"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE2, + ngx_conf_set_keyval_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_proxy_srv_conf_t, ssl_conf_commands), + &ngx_stream_proxy_ssl_conf_command_post }, + #if (T_NGX_SSL_NTLS) { ngx_string("proxy_enable_ntls"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, @@ -889,7 +911,7 @@ ngx_stream_proxy_init_upstream(ngx_stream_session_t *s) u->upstream_buf.last = p; } - if (c->buffer && c->buffer->pos < c->buffer->last) { + if (c->buffer && c->buffer->pos <= c->buffer->last) { ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream proxy add preread buffer: %uz", c->buffer->last - c->buffer->pos); @@ -903,6 +925,7 @@ ngx_stream_proxy_init_upstream(ngx_stream_session_t *s) *cl->buf = *c->buffer; cl->buf->tag = (ngx_buf_tag_t) &ngx_stream_proxy_module; + cl->buf->temporary = (cl->buf->pos == cl->buf->last) ? 0 : 1; cl->buf->flush = 1; cl->next = u->upstream_out; @@ -1057,6 +1080,17 @@ ngx_stream_proxy_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, } +static char * +ngx_stream_proxy_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) +{ +#ifndef SSL_CONF_FLAG_FILE + return "is not supported on this platform"; +#else + return NGX_CONF_OK; +#endif +} + + static void ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s) { @@ -1103,6 +1137,15 @@ ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s) } } + if (pscf->ssl_certificate && (pscf->ssl_certificate->lengths + || pscf->ssl_certificate_key->lengths)) + { + if (ngx_stream_proxy_ssl_certificate(s) != NGX_OK) { + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + } + if (pscf->ssl_session_reuse) { pc->ssl->save_session = ngx_stream_proxy_ssl_save_session; @@ -1290,6 +1333,50 @@ ngx_stream_proxy_ssl_name(ngx_stream_session_t *s) return NGX_OK; } + +static ngx_int_t +ngx_stream_proxy_ssl_certificate(ngx_stream_session_t *s) +{ + ngx_str_t cert, key; + ngx_connection_t *c; + ngx_stream_proxy_srv_conf_t *pscf; + + c = s->upstream->peer.connection; + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module); + + if (ngx_stream_complex_value(s, pscf->ssl_certificate, &cert) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream upstream ssl cert: \"%s\"", cert.data); + + if (*cert.data == '\0') { + return NGX_OK; + } + + if (ngx_stream_complex_value(s, pscf->ssl_certificate_key, &key) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream upstream ssl key: \"%s\"", key.data); + + if (ngx_ssl_connection_certificate(c, c->pool, &cert, &key, + pscf->ssl_passwords) + != NGX_OK) + { + return NGX_ERROR; + } + + return NGX_OK; +} + #endif @@ -1744,6 +1831,24 @@ ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream, } if (dst) { + + if (dst->type == SOCK_STREAM && pscf->half_close + && src->read->eof && !u->half_closed && !dst->buffered) + { + if (ngx_shutdown_socket(dst->fd, NGX_WRITE_SHUTDOWN) == -1) { + ngx_connection_error(c, ngx_socket_errno, + ngx_shutdown_socket_n " failed"); + + ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + u->half_closed = 1; + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, s->connection->log, 0, + "stream proxy %s socket shutdown", + from_upstream ? "client" : "upstream"); + } + if (ngx_handle_write_event(dst->write, 0) != NGX_OK) { ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); return; @@ -1822,6 +1927,13 @@ ngx_stream_proxy_test_finalize(ngx_stream_session_t *s, return NGX_DECLINED; } + if (pscf->half_close) { + /* avoid closing live connections until both read ends get EOF */ + if (!(c->read->eof && pc->read->eof && !c->buffered && !pc->buffered)) { + return NGX_DECLINED; + } + } + handler = c->log->handler; c->log->handler = NULL; @@ -2020,14 +2132,9 @@ ngx_stream_proxy_create_srv_conf(ngx_conf_t *cf) * * conf->ssl_protocols = 0; * conf->ssl_ciphers = { 0, NULL }; - * conf->ssl_name = NULL; * conf->ssl_trusted_certificate = { 0, NULL }; * conf->ssl_crl = { 0, NULL }; - * conf->ssl_certificate = { 0, NULL }; - * conf->ssl_certificate_key = { 0, NULL }; * - * conf->upload_rate = NULL; - * conf->download_rate = NULL; * conf->ssl = NULL; * conf->upstream = NULL; * conf->upstream_value = NULL; @@ -2037,6 +2144,8 @@ ngx_stream_proxy_create_srv_conf(ngx_conf_t *cf) conf->timeout = NGX_CONF_UNSET_MSEC; conf->next_upstream_timeout = NGX_CONF_UNSET_MSEC; conf->buffer_size = NGX_CONF_UNSET_SIZE; + conf->upload_rate = NGX_CONF_UNSET_PTR; + conf->download_rate = NGX_CONF_UNSET_PTR; conf->requests = NGX_CONF_UNSET_UINT; conf->responses = NGX_CONF_UNSET_UINT; conf->next_upstream_tries = NGX_CONF_UNSET_UINT; @@ -2044,14 +2153,19 @@ ngx_stream_proxy_create_srv_conf(ngx_conf_t *cf) conf->proxy_protocol = NGX_CONF_UNSET; conf->local = NGX_CONF_UNSET_PTR; conf->socket_keepalive = NGX_CONF_UNSET; + conf->half_close = NGX_CONF_UNSET; #if (NGX_STREAM_SSL) conf->ssl_enable = NGX_CONF_UNSET; conf->ssl_session_reuse = NGX_CONF_UNSET; + conf->ssl_name = NGX_CONF_UNSET_PTR; conf->ssl_server_name = NGX_CONF_UNSET; conf->ssl_verify = NGX_CONF_UNSET; conf->ssl_verify_depth = NGX_CONF_UNSET_UINT; + conf->ssl_certificate = NGX_CONF_UNSET_PTR; + conf->ssl_certificate_key = NGX_CONF_UNSET_PTR; conf->ssl_passwords = NGX_CONF_UNSET_PTR; + conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; #if (T_NGX_SSL_NTLS) conf->tls_method = NULL; @@ -2081,13 +2195,9 @@ ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 16384); - if (conf->upload_rate == NULL) { - conf->upload_rate = prev->upload_rate; - } + ngx_conf_merge_ptr_value(conf->upload_rate, prev->upload_rate, NULL); - if (conf->download_rate == NULL) { - conf->download_rate = prev->download_rate; - } + ngx_conf_merge_ptr_value(conf->download_rate, prev->download_rate, NULL); ngx_conf_merge_uint_value(conf->requests, prev->requests, 0); @@ -2107,6 +2217,8 @@ ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->socket_keepalive, prev->socket_keepalive, 0); + ngx_conf_merge_value(conf->half_close, prev->half_close, 0); + #if (NGX_STREAM_SSL) ngx_conf_merge_value(conf->ssl_enable, prev->ssl_enable, 0); @@ -2120,9 +2232,7 @@ ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers, "DEFAULT"); - if (conf->ssl_name == NULL) { - conf->ssl_name = prev->ssl_name; - } + ngx_conf_merge_ptr_value(conf->ssl_name, prev->ssl_name, NULL); ngx_conf_merge_value(conf->ssl_server_name, prev->ssl_server_name, 0); @@ -2136,14 +2246,17 @@ ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) 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_ptr_value(conf->ssl_certificate, + prev->ssl_certificate, NULL); - ngx_conf_merge_str_value(conf->ssl_certificate_key, - prev->ssl_certificate_key, ""); + ngx_conf_merge_ptr_value(conf->ssl_certificate_key, + prev->ssl_certificate_key, NULL); ngx_conf_merge_ptr_value(conf->ssl_passwords, prev->ssl_passwords, NULL); + ngx_conf_merge_ptr_value(conf->ssl_conf_commands, + prev->ssl_conf_commands, NULL); + if (conf->ssl_enable && ngx_stream_proxy_set_ssl(cf, conf) != NGX_OK) { return NGX_CONF_ERROR; } @@ -2192,26 +2305,46 @@ ngx_stream_proxy_set_ssl(ngx_conf_t *cf, ngx_stream_proxy_srv_conf_t *pscf) cln->handler = ngx_ssl_cleanup_ctx; cln->data = pscf->ssl; - if (pscf->ssl_certificate.len) { + if (ngx_ssl_ciphers(cf, pscf->ssl, &pscf->ssl_ciphers, 0) != NGX_OK) { + return NGX_ERROR; + } - if (pscf->ssl_certificate_key.len == 0) { + if (pscf->ssl_certificate) { + + if (pscf->ssl_certificate_key == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"proxy_ssl_certificate_key\" is defined " - "for certificate \"%V\"", &pscf->ssl_certificate); + "for certificate \"%V\"", + &pscf->ssl_certificate->value); return NGX_ERROR; } + if (pscf->ssl_certificate->lengths + || pscf->ssl_certificate_key->lengths) + { + pscf->ssl_passwords = + ngx_ssl_preserve_passwords(cf, pscf->ssl_passwords); + if (pscf->ssl_passwords == NULL) { + return NGX_ERROR; + } + + } else { + #if (T_NGX_SSL_NTLS) - if (ngx_ssl_certificate(cf, pscf->ssl, &pscf->ssl_certificate, - &pscf->ssl_certificate_key, pscf->ssl_passwords, - SSL_NORMAL_CERT) + if (ngx_ssl_certificate(cf, pscf->ssl, + &pscf->ssl_certificate->value, + &pscf->ssl_certificate_key->value, + SSL_NORMAL_CERT) #else - if (ngx_ssl_certificate(cf, pscf->ssl, &pscf->ssl_certificate, - &pscf->ssl_certificate_key, pscf->ssl_passwords) + if (ngx_ssl_certificate(cf, pscf->ssl, + &pscf->ssl_certificate->value, + &pscf->ssl_certificate_key->value, + pscf->ssl_passwords) #endif - != NGX_OK) - { - return NGX_ERROR; + != NGX_OK) + { + return NGX_ERROR; + } } } @@ -2254,10 +2387,6 @@ ngx_stream_proxy_set_ssl(ngx_conf_t *cf, ngx_stream_proxy_srv_conf_t *pscf) } #endif - if (ngx_ssl_ciphers(cf, pscf->ssl, &pscf->ssl_ciphers, 0) != NGX_OK) { - return NGX_ERROR; - } - if (pscf->ssl_verify) { if (pscf->ssl_trusted_certificate.len == 0) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, @@ -2284,6 +2413,12 @@ ngx_stream_proxy_set_ssl(ngx_conf_t *cf, ngx_stream_proxy_srv_conf_t *pscf) return NGX_ERROR; } + if (ngx_ssl_conf_commands(cf, pscf->ssl, pscf->ssl_conf_commands) + != NGX_OK) + { + return NGX_ERROR; + } + return NGX_OK; } diff --git a/src/stream/ngx_stream_script.c b/src/stream/ngx_stream_script.c index a15f772b58..c447e152f8 100644 --- a/src/stream/ngx_stream_script.c +++ b/src/stream/ngx_stream_script.c @@ -252,7 +252,7 @@ ngx_stream_set_complex_value_slot(ngx_conf_t *cf, ngx_command_t *cmd, cv = (ngx_stream_complex_value_t **) (p + cmd->offset); - if (*cv != NULL) { + if (*cv != NGX_CONF_UNSET_PTR && *cv != NULL) { return "is duplicate"; } @@ -277,6 +277,44 @@ ngx_stream_set_complex_value_slot(ngx_conf_t *cf, ngx_command_t *cmd, } +char * +ngx_stream_set_complex_value_zero_slot(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + char *p = conf; + + ngx_str_t *value; + ngx_stream_complex_value_t **cv; + ngx_stream_compile_complex_value_t ccv; + + cv = (ngx_stream_complex_value_t **) (p + cmd->offset); + + if (*cv != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + *cv = ngx_palloc(cf->pool, sizeof(ngx_stream_complex_value_t)); + if (*cv == NULL) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = *cv; + ccv.zero = 1; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + char * ngx_stream_set_complex_value_size_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) diff --git a/src/stream/ngx_stream_script.h b/src/stream/ngx_stream_script.h index a481ca3abc..d8f3740470 100644 --- a/src/stream/ngx_stream_script.h +++ b/src/stream/ngx_stream_script.h @@ -112,6 +112,8 @@ ngx_int_t ngx_stream_compile_complex_value( ngx_stream_compile_complex_value_t *ccv); char *ngx_stream_set_complex_value_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +char *ngx_stream_set_complex_value_zero_slot(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); char *ngx_stream_set_complex_value_size_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); diff --git a/src/stream/ngx_stream_set_module.c b/src/stream/ngx_stream_set_module.c new file mode 100644 index 0000000000..9b34a6099f --- /dev/null +++ b/src/stream/ngx_stream_set_module.c @@ -0,0 +1,226 @@ + +/* + * Copyright (C) Pavel Pautov + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + ngx_int_t index; + ngx_stream_set_variable_pt set_handler; + uintptr_t data; + ngx_stream_complex_value_t value; +} ngx_stream_set_cmd_t; + + +typedef struct { + ngx_array_t commands; +} ngx_stream_set_srv_conf_t; + + +static ngx_int_t ngx_stream_set_handler(ngx_stream_session_t *s); +static ngx_int_t ngx_stream_set_var(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_set_init(ngx_conf_t *cf); +static void *ngx_stream_set_create_srv_conf(ngx_conf_t *cf); +static char *ngx_stream_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +static ngx_command_t ngx_stream_set_commands[] = { + + { ngx_string("set"), + NGX_STREAM_SRV_CONF|NGX_CONF_TAKE2, + ngx_stream_set, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_set_module_ctx = { + NULL, /* preconfiguration */ + ngx_stream_set_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_set_create_srv_conf, /* create server configuration */ + NULL /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_set_module = { + NGX_MODULE_V1, + &ngx_stream_set_module_ctx, /* module context */ + ngx_stream_set_commands, /* module directives */ + NGX_STREAM_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_stream_set_handler(ngx_stream_session_t *s) +{ + ngx_str_t str; + ngx_uint_t i; + ngx_stream_set_cmd_t *cmds; + ngx_stream_set_srv_conf_t *scf; + ngx_stream_variable_value_t vv; + + scf = ngx_stream_get_module_srv_conf(s, ngx_stream_set_module); + cmds = scf->commands.elts; + vv = ngx_stream_variable_null_value; + + for (i = 0; i < scf->commands.nelts; i++) { + if (ngx_stream_complex_value(s, &cmds[i].value, &str) != NGX_OK) { + return NGX_ERROR; + } + + if (cmds[i].set_handler != NULL) { + vv.len = str.len; + vv.data = str.data; + cmds[i].set_handler(s, &vv, cmds[i].data); + + } else { + s->variables[cmds[i].index].len = str.len; + s->variables[cmds[i].index].valid = 1; + s->variables[cmds[i].index].no_cacheable = 0; + s->variables[cmds[i].index].not_found = 0; + s->variables[cmds[i].index].data = str.data; + } + } + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_stream_set_var(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, + uintptr_t data) +{ + *v = ngx_stream_variable_null_value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_set_init(ngx_conf_t *cf) +{ + ngx_stream_handler_pt *h; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + h = ngx_array_push(&cmcf->phases[NGX_STREAM_PREACCESS_PHASE].handlers); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_stream_set_handler; + + return NGX_OK; +} + + +static void * +ngx_stream_set_create_srv_conf(ngx_conf_t *cf) +{ + ngx_stream_set_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_set_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->commands = { NULL }; + */ + + return conf; +} + + +static char * +ngx_stream_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_set_srv_conf_t *scf = conf; + + ngx_str_t *args; + ngx_int_t index; + ngx_stream_set_cmd_t *set_cmd; + ngx_stream_variable_t *v; + ngx_stream_compile_complex_value_t ccv; + + args = cf->args->elts; + + if (args[1].data[0] != '$') { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid variable name \"%V\"", &args[1]); + return NGX_CONF_ERROR; + } + + args[1].len--; + args[1].data++; + + v = ngx_stream_add_variable(cf, &args[1], + NGX_STREAM_VAR_CHANGEABLE|NGX_STREAM_VAR_WEAK); + if (v == NULL) { + return NGX_CONF_ERROR; + } + + index = ngx_stream_get_variable_index(cf, &args[1]); + if (index == NGX_ERROR) { + return NGX_CONF_ERROR; + } + + if (v->get_handler == NULL) { + v->get_handler = ngx_stream_set_var; + } + + if (scf->commands.elts == NULL) { + if (ngx_array_init(&scf->commands, cf->pool, 1, + sizeof(ngx_stream_set_cmd_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + } + + set_cmd = ngx_array_push(&scf->commands); + if (set_cmd == NULL) { + return NGX_CONF_ERROR; + } + + set_cmd->index = index; + set_cmd->set_handler = v->set_handler; + set_cmd->data = v->data; + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &args[2]; + ccv.complex_value = &set_cmd->value; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index 4a698fc60e..8476edd33c 100755 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -23,7 +23,13 @@ static ngx_int_t ngx_stream_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c); static void ngx_stream_ssl_handshake_handler(ngx_connection_t *c); #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME -int ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg); +static int ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, + void *arg); +#endif +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation +static int ngx_stream_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, + const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg); #endif #ifdef SSL_R_CERT_CB_ERROR static int ngx_stream_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg); @@ -45,6 +51,12 @@ static char *ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_stream_ssl_alpn(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +static char *ngx_stream_ssl_conf_command_check(ngx_conf_t *cf, void *post, + void *data); + static ngx_int_t ngx_stream_ssl_init(ngx_conf_t *cf); #if (T_NGX_STREAM_SNI) @@ -82,6 +94,10 @@ static ngx_conf_enum_t ngx_stream_ssl_verify[] = { }; +static ngx_conf_post_t ngx_stream_ssl_conf_command_post = + { ngx_stream_ssl_conf_command_check }; + + static ngx_command_t ngx_stream_ssl_commands[] = { { ngx_string("ssl_handshake_timeout"), @@ -251,6 +267,20 @@ static ngx_command_t ngx_stream_ssl_commands[] = { offsetof(ngx_stream_ssl_conf_t, crl), NULL }, + { ngx_string("ssl_conf_command"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE2, + ngx_conf_set_keyval_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, conf_commands), + &ngx_stream_ssl_conf_command_post }, + + { ngx_string("ssl_alpn"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, + ngx_stream_ssl_alpn, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + #if (T_NGX_STREAM_SNI) { ngx_string("ssl_sni_force"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, @@ -303,6 +333,9 @@ static ngx_stream_variable_t ngx_stream_ssl_vars[] = { { ngx_string("ssl_ciphers"), NULL, ngx_stream_ssl_variable, (uintptr_t) ngx_ssl_get_ciphers, NGX_STREAM_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_curve"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_curve, NGX_STREAM_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_curves"), NULL, ngx_stream_ssl_variable, (uintptr_t) ngx_ssl_get_curves, NGX_STREAM_VAR_CHANGEABLE, 0 }, @@ -315,6 +348,9 @@ static ngx_stream_variable_t ngx_stream_ssl_vars[] = { { ngx_string("ssl_server_name"), NULL, ngx_stream_ssl_variable, (uintptr_t) ngx_ssl_get_server_name, NGX_STREAM_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_alpn_protocol"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_alpn_protocol, NGX_STREAM_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_client_cert"), NULL, ngx_stream_ssl_variable, (uintptr_t) ngx_ssl_get_certificate, NGX_STREAM_VAR_CHANGEABLE, 0 }, @@ -623,7 +659,7 @@ ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) #else #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME -int +static int ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) { return SSL_TLSEXT_ERR_OK; @@ -632,9 +668,50 @@ ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) #endif #endif + +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + +static int +ngx_stream_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, unsigned int inlen, + void *arg) +{ + ngx_str_t *alpn; +#if (NGX_DEBUG) + unsigned int i; + ngx_connection_t *c; + + c = ngx_ssl_get_connection(ssl_conn); + + for (i = 0; i < inlen; i += in[i] + 1) { + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL ALPN supported by client: %*s", + (size_t) in[i], &in[i + 1]); + } + +#endif + + alpn = arg; + + if (SSL_select_next_proto((unsigned char **) out, outlen, alpn->data, + alpn->len, in, inlen) + != OPENSSL_NPN_NEGOTIATED) + { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + ngx_log_debug2(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL ALPN selected: %*s", (size_t) *outlen, *out); + + return SSL_TLSEXT_ERR_OK; +} + +#endif + + #ifdef SSL_R_CERT_CB_ERROR -int +static int ngx_stream_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg) { ngx_str_t cert, key; @@ -791,6 +868,7 @@ ngx_stream_ssl_create_conf(ngx_conf_t *cf) * scf->client_certificate = { 0, NULL }; * scf->trusted_certificate = { 0, NULL }; * scf->crl = { 0, NULL }; + * scf->alpn = { 0, NULL }; * scf->ciphers = { 0, NULL }; * scf->shm_zone = NULL; */ @@ -799,6 +877,7 @@ ngx_stream_ssl_create_conf(ngx_conf_t *cf) scf->certificates = NGX_CONF_UNSET_PTR; scf->certificate_keys = NGX_CONF_UNSET_PTR; scf->passwords = NGX_CONF_UNSET_PTR; + scf->conf_commands = NGX_CONF_UNSET_PTR; scf->prefer_server_ciphers = NGX_CONF_UNSET; scf->verify = NGX_CONF_UNSET_UINT; scf->verify_depth = NGX_CONF_UNSET_UINT; @@ -867,12 +946,15 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_str_value(conf->trusted_certificate, prev->trusted_certificate, ""); ngx_conf_merge_str_value(conf->crl, prev->crl, ""); + ngx_conf_merge_str_value(conf->alpn, prev->alpn, ""); ngx_conf_merge_str_value(conf->ecdh_curve, prev->ecdh_curve, NGX_DEFAULT_ECDH_CURVE); ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS); + ngx_conf_merge_ptr_value(conf->conf_commands, prev->conf_commands, NULL); + #if (T_NGX_STREAM_SNI) ngx_conf_merge_value(conf->sni_force, prev->sni_force, 0); #endif @@ -993,6 +1075,20 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) #endif #endif +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + if (conf->alpn.len) { + SSL_CTX_set_alpn_select_cb(conf->ssl.ctx, ngx_stream_ssl_alpn_select, + &conf->alpn); + } +#endif + + if (ngx_ssl_ciphers(cf, &conf->ssl, &conf->ciphers, + conf->prefer_server_ciphers) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + if (ngx_stream_ssl_compile_certificates(cf, conf) != NGX_OK) { return NGX_CONF_ERROR; } @@ -1045,12 +1141,6 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) } } #endif - if (ngx_ssl_ciphers(cf, &conf->ssl, &conf->ciphers, - conf->prefer_server_ciphers) - != NGX_OK) - { - return NGX_CONF_ERROR; - } if (conf->verify) { @@ -1122,6 +1212,10 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } + if (ngx_ssl_conf_commands(cf, &conf->ssl, conf->conf_commands) != NGX_OK) { + return NGX_CONF_ERROR; + } + return NGX_CONF_OK; } @@ -1376,6 +1470,71 @@ ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } +static char * +ngx_stream_ssl_alpn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + + ngx_stream_ssl_conf_t *scf = conf; + + u_char *p; + size_t len; + ngx_str_t *value; + ngx_uint_t i; + + if (scf->alpn.len) { + return "is duplicate"; + } + + value = cf->args->elts; + + len = 0; + + for (i = 1; i < cf->args->nelts; i++) { + + if (value[i].len > 255) { + return "protocol too long"; + } + + len += value[i].len + 1; + } + + scf->alpn.data = ngx_pnalloc(cf->pool, len); + if (scf->alpn.data == NULL) { + return NGX_CONF_ERROR; + } + + p = scf->alpn.data; + + for (i = 1; i < cf->args->nelts; i++) { + *p++ = value[i].len; + p = ngx_cpymem(p, value[i].data, value[i].len); + } + + scf->alpn.len = len; + + return NGX_CONF_OK; + +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"ssl_alpn\" directive requires OpenSSL " + "with ALPN support"); + return NGX_CONF_ERROR; +#endif +} + + +static char * +ngx_stream_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) +{ +#ifndef SSL_CONF_FLAG_FILE + return "is not supported on this platform"; +#else + return NGX_CONF_OK; +#endif +} + + static ngx_int_t ngx_stream_ssl_init(ngx_conf_t *cf) { diff --git a/src/stream/ngx_stream_ssl_module.h b/src/stream/ngx_stream_ssl_module.h index 34b7a5f0b9..d489499449 100755 --- a/src/stream/ngx_stream_ssl_module.h +++ b/src/stream/ngx_stream_ssl_module.h @@ -42,10 +42,12 @@ typedef struct { ngx_str_t client_certificate; ngx_str_t trusted_certificate; ngx_str_t crl; + ngx_str_t alpn; ngx_str_t ciphers; ngx_array_t *passwords; + ngx_array_t *conf_commands; ngx_shm_zone_t *shm_zone; diff --git a/src/stream/ngx_stream_upstream.h b/src/stream/ngx_stream_upstream.h index 53bb5a178d..c8d7921460 100644 --- a/src/stream/ngx_stream_upstream.h +++ b/src/stream/ngx_stream_upstream.h @@ -142,6 +142,7 @@ typedef struct { ngx_stream_upstream_state_t *state; unsigned connected:1; unsigned proxy_protocol:1; + unsigned half_closed:1; #if (T_NGX_MULTI_UPSTREAM) unsigned multi:1; diff --git a/src/stream/ngx_stream_upstream_round_robin.c b/src/stream/ngx_stream_upstream_round_robin.c index c2076673a7..ae3bf37a68 100644 --- a/src/stream/ngx_stream_upstream_round_robin.c +++ b/src/stream/ngx_stream_upstream_round_robin.c @@ -10,8 +10,8 @@ #include -#define ngx_stream_upstream_tries(p) ((p)->number \ - + ((p)->next ? (p)->next->number : 0)) +#define ngx_stream_upstream_tries(p) ((p)->tries \ + + ((p)->next ? (p)->next->tries : 0)) static ngx_stream_upstream_rr_peer_t *ngx_stream_upstream_get_peer( @@ -38,7 +38,7 @@ ngx_stream_upstream_init_round_robin(ngx_conf_t *cf, ngx_stream_upstream_srv_conf_t *us) { ngx_url_t u; - ngx_uint_t i, j, n, w; + ngx_uint_t i, j, n, w, t; ngx_stream_upstream_server_t *server; ngx_stream_upstream_rr_peer_t *peer, **peerp; ngx_stream_upstream_rr_peers_t *peers, *backup; @@ -50,6 +50,7 @@ ngx_stream_upstream_init_round_robin(ngx_conf_t *cf, n = 0; w = 0; + t = 0; for (i = 0; i < us->servers->nelts; i++) { if (server[i].backup) { @@ -58,6 +59,10 @@ ngx_stream_upstream_init_round_robin(ngx_conf_t *cf, n += server[i].naddrs; w += server[i].naddrs * server[i].weight; + + if (!server[i].down) { + t += server[i].naddrs; + } } if (n == 0) { @@ -81,6 +86,7 @@ ngx_stream_upstream_init_round_robin(ngx_conf_t *cf, peers->number = n; peers->weighted = (w != n); peers->total_weight = w; + peers->tries = t; peers->name = &us->host; n = 0; @@ -116,6 +122,7 @@ ngx_stream_upstream_init_round_robin(ngx_conf_t *cf, n = 0; w = 0; + t = 0; for (i = 0; i < us->servers->nelts; i++) { if (!server[i].backup) { @@ -124,6 +131,10 @@ ngx_stream_upstream_init_round_robin(ngx_conf_t *cf, n += server[i].naddrs; w += server[i].naddrs * server[i].weight; + + if (!server[i].down) { + t += server[i].naddrs; + } } if (n == 0) { @@ -145,6 +156,7 @@ ngx_stream_upstream_init_round_robin(ngx_conf_t *cf, backup->number = n; backup->weighted = (w != n); backup->total_weight = w; + backup->tries = t; backup->name = &us->host; n = 0; @@ -220,6 +232,7 @@ ngx_stream_upstream_init_round_robin(ngx_conf_t *cf, peers->number = n; peers->weighted = 0; peers->total_weight = n; + peers->tries = n; peers->name = &us->host; peerp = &peers->peer; @@ -342,6 +355,7 @@ ngx_stream_upstream_create_round_robin_peer(ngx_stream_session_t *s, peers->single = (ur->naddrs == 1); peers->number = ur->naddrs; + peers->tries = ur->naddrs; peers->name = &ur->host; if (ur->sockaddr) { diff --git a/src/stream/ngx_stream_upstream_round_robin.h b/src/stream/ngx_stream_upstream_round_robin.h index 35d9fce6ca..bd96667ba3 100644 --- a/src/stream/ngx_stream_upstream_round_robin.h +++ b/src/stream/ngx_stream_upstream_round_robin.h @@ -66,6 +66,7 @@ struct ngx_stream_upstream_rr_peers_s { #endif ngx_uint_t total_weight; + ngx_uint_t tries; unsigned single:1; unsigned weighted:1; diff --git a/src/stream/ngx_stream_write_filter_module.c b/src/stream/ngx_stream_write_filter_module.c index 24326c60e1..156a61c3d5 100644 --- a/src/stream/ngx_stream_write_filter_module.c +++ b/src/stream/ngx_stream_write_filter_module.c @@ -234,7 +234,8 @@ ngx_stream_write_filter(ngx_stream_session_t *s, ngx_chain_t *in, if (size == 0 && !(c->buffered & NGX_LOWLEVEL_BUFFERED) - && !(last && c->need_last_buf)) + && !(last && c->need_last_buf) + && !(c->type == SOCK_DGRAM && flush)) { if (last || flush || sync) { for (cl = *out; cl; /* void */) { diff --git a/tests/nginx-tests/nginx-tests/README b/tests/nginx-tests/nginx-tests/README index e5fe008bbc..f43c5860bf 100644 --- a/tests/nginx-tests/nginx-tests/README +++ b/tests/nginx-tests/nginx-tests/README @@ -18,6 +18,10 @@ TEST_NGINX_BINARY Sets path to nginx binary to be tested, defaults to "../nginx/objs/nginx". +TEST_NGINX_MODULES + + Sets path to modules directory, defaults to dirname of TEST_NGINX_BINARY. + TEST_NGINX_VERBOSE Be a bit more verbose (in particular, print requests sent and responses @@ -36,4 +40,16 @@ TEST_NGINX_UNSAFE Run unsafe tests. +TEST_NGINX_GLOBALS + + Sets additional directives in main context. + +TEST_NGINX_GLOBALS_HTTP + + Sets additional directives in http context. + +TEST_NGINX_GLOBALS_STREAM + + Sets additional directives in stream context. + Happy testing! diff --git a/tests/nginx-tests/nginx-tests/access_log.t b/tests/nginx-tests/nginx-tests/access_log.t index 39f2c20de5..b414525adb 100644 --- a/tests/nginx-tests/nginx-tests/access_log.t +++ b/tests/nginx-tests/nginx-tests/access_log.t @@ -22,7 +22,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http rewrite gzip/)->plan(18) +my $t = Test::Nginx->new()->has(qw/http rewrite gzip/)->plan(19) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -39,11 +39,12 @@ http { log_format test "$uri:$status"; log_format long "long line $uri:$status"; + log_format addr "$remote_addr:$remote_port:$server_addr:$server_port"; log_format binary $binary_remote_addr; - log_format default escape=default $arg_a$arg_b$arg_c; - log_format none escape=none $arg_a$arg_b$arg_c; - log_format json escape=json $arg_a$arg_b$arg_c; + log_format default escape=default $uri$arg_b$arg_c; + log_format none escape=none $uri$arg_b$arg_c; + log_format json escape=json $uri$arg_b$arg_c; server { listen 127.0.0.1:8080; @@ -103,6 +104,10 @@ http { return 200 OK; } + location /addr { + access_log %%TESTDIR%%/addr.log addr; + } + location /binary { access_log %%TESTDIR%%/binary.log binary; } @@ -157,9 +162,17 @@ http_get('/varlog?logname='); http_get('/varlog?logname=0'); http_get('/varlog?logname=filename'); +my $s = http('', start => 1); +http_get('/addr', socket => $s); +my $addr = $s->sockhost(); +my $port = $s->sockport(); +my $saddr = $s->peerhost(); +my $sport = $s->peerport(); + http_get('/binary'); -http_get('/escape?a="1 \\ ' . pack("n", 0x1b1c) . ' "&c=2'); +# /escape/"1 %1B%1C "?c=2 +http_get('/escape/%221%20%1B%1C%20%22?c=2'); http_get('/cache?logname=lru'); http_get('/cache?logname=lru'); @@ -205,8 +218,6 @@ $t->stop(); # verify that by default, 'combined' format is used, 'off' disables logging -my $addr = IO::Socket::INET->new(LocalAddr => '127.0.0.1')->sockhost(); - like($t->read_file('combined.log'), qr!^\Q$addr - - [\E .* \Q] "GET /combined HTTP/1.0" 200 2 "-" "-"\E$!x, @@ -251,6 +262,8 @@ is($t->read_file('long.log'), "long line /multi:200\n", 'long line format'); is($t->read_file('varlog_0'), "/varlog:200\n", 'varlog literal zero name'); is($t->read_file('varlog_filename'), "/varlog:200\n", 'varlog good name'); +is($t->read_file('addr.log'), "$addr:$port:$saddr:$sport\n", 'addr'); + # binary data is escaped # that's "\\x7F\\x00\\x00\\x01\n" in $binary_remote_addr for "127.0.0.1" @@ -261,11 +274,11 @@ is($t->read_file('binary.log'), "$expected\n", 'binary'); # characters escaping is($t->read_file('test.log'), - '\x221 \x5C \x1B\x1C \x22-2' . "\n", 'escape - default'); + '/escape/\x221 \x1B\x1C \x22-2' . "\n", 'escape - default'); is($t->read_file('none.log'), - '"1 \\ ' . pack("n", 0x1b1c) . " \"2\n", 'escape - none'); + "/escape/\"1 \x1B\x1C \"2\n", 'escape - none'); is($t->read_file('json.log'), - '\"1 \\\\ \u001B\u001C \"2' . "\n", 'escape - json'); + '/escape/\"1 \u001B\u001C \"2' . "\n", 'escape - json'); SKIP: { skip 'win32', 4 if $^O eq 'MSWin32'; diff --git a/tests/nginx-tests/nginx-tests/auth_basic.t b/tests/nginx-tests/nginx-tests/auth_basic.t index 32f3b9efa7..e8957edf36 100644 --- a/tests/nginx-tests/nginx-tests/auth_basic.t +++ b/tests/nginx-tests/nginx-tests/auth_basic.t @@ -23,7 +23,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http auth_basic/)->plan(21) +my $t = Test::Nginx->new()->has(qw/http auth_basic/)->plan(24) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -48,6 +48,12 @@ http { auth_basic off; alias %%TESTDIR%%/; } + + location /var { + # prepended with conf_prefix + auth_basic_user_file $arg_f; + alias %%TESTDIR%%/; + } } } } @@ -112,6 +118,13 @@ like(http_get_auth('/', 'sha3', '1'), qr!401 Unauthorized!, 'sha broken 2'); like(http_get_auth('/', 'notfound', '1'), qr!401 Unauthorized!, 'not found'); like(http_get('/inner/'), qr!SEETHIS!, 'inner off'); +like(http_get_auth('/var/?f=htpasswd', 'apr1', 'password'), qr!SEETHIS!, + 'user file variable'); +unlike(http_get_auth('/var/?f=nx', 'apr1', 'password'), qr!SEETHIS!, + 'user file variable not found'); +unlike(http_get_auth('/var/', 'apr1', 'password'), qr!SEETHIS!, + 'user file variable bad value'); + ############################################################################### sub http_get_auth { diff --git a/tests/nginx-tests/nginx-tests/auth_delay.t b/tests/nginx-tests/nginx-tests/auth_delay.t new file mode 100644 index 0000000000..90034eb4b6 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/auth_delay.t @@ -0,0 +1,85 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for auth_delay directive using auth basic module. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use MIME::Base64; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http auth_basic/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + auth_delay 2s; + + auth_basic "closed site"; + auth_basic_user_file %%TESTDIR%%/htpasswd; + } + } +} + +EOF + +$t->write_file('index.html', ''); +$t->write_file('htpasswd', 'user:' . '{PLAIN}good' . "\n"); + +$t->run()->plan(4); + +############################################################################### + +my $t1 = time(); +like(http_get_auth('/', 'user', 'bad'), qr/401 Unauthorize/, 'not authorized'); +cmp_ok(time() - $t1, '>=', 2, 'auth delay'); + +$t1 = time(); +like(http_get_auth('/', 'user', 'good'), qr/200 OK/, 'authorized'); +cmp_ok(time() - $t1, '<', 2, 'no delay'); + +############################################################################### + +sub http_get_auth { + my ($url, $user, $password) = @_; + + my $auth = encode_base64($user . ':' . $password, ''); + + return http(<new() ->has(qw/http rewrite proxy cache fastcgi auth_basic auth_request/) - ->plan(19); + ->plan(20); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -129,6 +129,20 @@ http { proxy_cache_valid 1m; } + location /proxy-multi { + auth_request /auth-proxy-multi; + } + location = /auth-proxy-multi { + proxy_pass http://127.0.0.1:8080/auth-multi; + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + } + location = /auth-multi { + add_header WWW-Authenticate foo always; + add_header WWW-Authenticate bar always; + return 401; + } + location /fastcgi { auth_request /auth-fastcgi; } @@ -187,6 +201,16 @@ like(http_get('/proxy-cache'), qr/ 404 /, 'proxy auth cached'); like(http_post_big('/proxy-double'), qr/ 204 /, 'proxy auth with body read'); +# Multiple WWW-Authenticate headers (ticket #485). + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like(http_get('/proxy-multi-auth'), qr/WWW-Authenticate: foo.*bar/s, + 'multiple www-authenticate headers'); + +} + SKIP: { eval { require FCGI; }; skip 'FCGI not installed', 2 if $@; diff --git a/tests/nginx-tests/nginx-tests/binary_upgrade.t b/tests/nginx-tests/nginx-tests/binary_upgrade.t new file mode 100644 index 0000000000..d4e7c1d827 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/binary_upgrade.t @@ -0,0 +1,94 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for binary upgrade. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +plan(skip_all => 'can leave orphaned process group') + unless $ENV{TEST_NGINX_UNSAFE}; + +my $t = Test::Nginx->new(qr/http unix/)->plan(4) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen unix:%%TESTDIR%%/unix.sock; + server_name localhost; + } +} + +EOF + +my $d = $t->testdir(); + +$t->run(); + +############################################################################### + +my $pid = $t->read_file('nginx.pid'); +ok($pid, 'master pid'); + +kill 'USR2', $pid; + +for (1 .. 30) { + last if -e "$d/nginx.pid" && -e "$d/nginx.pid.oldbin"; + select undef, undef, undef, 0.2 +} + +isnt($t->read_file('nginx.pid'), $pid, 'master pid changed'); + +kill 'QUIT', $pid; + +for (1 .. 30) { + last if ! -e "$d/nginx.pid.oldbin"; + select undef, undef, undef, 0.2 +} + +ok(-e "$d/unix.sock", 'unix socket exists on old master shutdown'); + +# unix socket on new master termination + +$pid = $t->read_file('nginx.pid'); + +kill 'USR2', $pid; + +for (1 .. 30) { + last if -e "$d/nginx.pid" && -e "$d/nginx.pid.oldbin"; + select undef, undef, undef, 0.2 +} + +kill 'TERM', $t->read_file('nginx.pid'); + +for (1 .. 30) { + last if ! -e "$d/nginx.pid.oldbin"; + select undef, undef, undef, 0.2 +} + +ok(-e "$d/unix.sock", 'unix socket exists on new master termination'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/body.t b/tests/nginx-tests/nginx-tests/body.t index 9cbc51536e..dc487cc803 100644 --- a/tests/nginx-tests/nginx-tests/body.t +++ b/tests/nginx-tests/nginx-tests/body.t @@ -22,7 +22,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http proxy rewrite/)->plan(13); +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/)->plan(15); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -71,12 +71,19 @@ http { add_header X-Body-File "$request_body_file"; proxy_pass http://127.0.0.1:8081; } + location /large { + client_max_body_size 1k; + proxy_pass http://127.0.0.1:8081; + } location /discard { return 200 "TEST\n"; } location /next { proxy_pass http://u/; } + location /redirect { + error_page 404 http://example.com/; + } } server { @@ -121,6 +128,8 @@ like(read_body_file(http_get_body('/b', '0123456789' x 512)), like(http_get_body('/single', '0123456789' x 128), qr/X-Body: (0123456789){128}\x0d?$/ms, 'body in single buffer'); +like(http_get_body('/large', '0123456789' x 128), qr/ 413 /, 'body too large'); + # pipelined requests like(http_get_body('/', '0123456789', '0123456789' x 128, '0123456789' x 512, @@ -159,6 +168,19 @@ like( like(http_get_body('/next', '0123456789'), qr/X-Body: 0123456789\x0d?$/ms, 'body next upstream'); +# discarded request body in redirect via error_page + +unlike( + http( + 'POST /redirect HTTP/1.1' . CRLF + . 'Host: localhost' . CRLF + . 'Content-Length: 10' . CRLF . CRLF + . '0123456789' . + 'GET /next HTTP/1.0' . CRLF . CRLF + ), + qr/400 Bad Request/ms, 'redirect - discard request body' +); + ############################################################################### sub read_body_file { diff --git a/tests/nginx-tests/nginx-tests/body_chunked.t b/tests/nginx-tests/nginx-tests/body_chunked.t index eef7aa1b09..2993b65bc2 100644 --- a/tests/nginx-tests/nginx-tests/body_chunked.t +++ b/tests/nginx-tests/nginx-tests/body_chunked.t @@ -22,7 +22,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http proxy rewrite/)->plan(10); +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/)->plan(18); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -66,6 +66,10 @@ http { add_header X-Body-File "$request_body_file"; proxy_pass http://127.0.0.1:8081; } + location /large { + client_max_body_size 1k; + proxy_pass http://127.0.0.1:8081; + } location /discard { return 200 "TEST\n"; } @@ -114,6 +118,8 @@ like(read_body_file(http_get_body('/b', '0123456789' x 512)), like(http_get_body('/single', '0123456789' x 128), qr/X-Body: (0123456789){128}\x0d?$/ms, 'body in single buffer'); +like(http_get_body('/large', '0123456789' x 128), qr/ 413 /, 'body too large'); + # pipelined requests like(http_get_body('/', '0123456789', '0123456789' x 128, '0123456789' x 512, @@ -128,11 +134,66 @@ like(http_get_body('/discard', '0123456789' x 128, '0123456789' x 512, '0123456789', 'foobar'), qr/(TEST.*){4}/ms, 'chunked body discard 2'); +# invalid chunks + +like( + http( + 'GET / HTTP/1.1' . CRLF + . 'Host: localhost' . CRLF + . 'Connection: close' . CRLF + . 'Transfer-Encoding: chunked' . CRLF . CRLF + . '4' . CRLF + . 'SEE-THIS' . CRLF + . '0' . CRLF . CRLF + ), + qr/400 Bad/, 'runaway chunk' +); + +like( + http( + 'GET /discard HTTP/1.1' . CRLF + . 'Host: localhost' . CRLF + . 'Connection: close' . CRLF + . 'Transfer-Encoding: chunked' . CRLF . CRLF + . '4' . CRLF + . 'SEE-THIS' . CRLF + . '0' . CRLF . CRLF + ), + qr/400 Bad/, 'runaway chunk discard' +); + # proxy_next_upstream like(http_get_body('/next', '0123456789'), qr/X-Body: 0123456789\x0d?$/ms, 'body chunked next upstream'); +# invalid Transfer-Encoding + +like(http_transfer_encoding('identity'), qr/501 Not Implemented/, + 'transfer encoding identity'); + +like(http_transfer_encoding("chunked\nTransfer-Encoding: chunked"), + qr/400 Bad/, 'transfer encoding repeat'); + +like(http_transfer_encoding('chunked, identity'), qr/501 Not Implemented/, + 'transfer encoding list'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.1'); + +like(http_transfer_encoding("chunked\nContent-Length: 5"), qr/400 Bad/, + 'transfer encoding with content-length'); + +} + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.2'); + +like(http_transfer_encoding("chunked", "1.0"), qr/400 Bad/, + 'transfer encoding in HTTP/1.0 requests'); + +} + ############################################################################### sub read_body_file { @@ -168,4 +229,15 @@ sub http_get_body { ); } +sub http_transfer_encoding { + my ($encoding, $version) = @_; + $version ||= "1.1"; + + http("GET / HTTP/$version" . CRLF + . "Host: localhost" . CRLF + . "Connection: close" . CRLF + . "Transfer-Encoding: $encoding" . CRLF . CRLF + . "0" . CRLF . CRLF); +} + ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/config_dump.t b/tests/nginx-tests/nginx-tests/config_dump.t index e9c17397ad..2d50c04d79 100644 --- a/tests/nginx-tests/nginx-tests/config_dump.t +++ b/tests/nginx-tests/nginx-tests/config_dump.t @@ -25,14 +25,13 @@ select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/http map/); -plan(skip_all => 'no config dump') unless $t->has_version('1.9.2'); - -$t->plan(10)->write_file_expand('nginx.conf', <<'EOF'); +$t->plan(13)->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% daemon off; +include %%TESTDIR%%/inc.conf; include %%TESTDIR%%/inc.conf; events { @@ -45,6 +44,7 @@ http { default 0; foo bar; include map.conf; + include map.conf; } upstream u { @@ -77,6 +77,10 @@ like($dump, qr!^# configuration file $d/inc.conf:$!m, 'inc.conf found'); like($dump, qr!^# configuration file $d/inc2.conf:$!m, 'inc2.conf found'); like($dump, qr!^# configuration file $d/map.conf:$!m, 'map.conf found'); +unlike($dump, qr!(# configuration file $d/inc.conf:).*\1!s, 'inc.conf uniq'); +unlike($dump, qr!(# configuration file $d/inc2.conf:).*\1!s, 'inc2.conf uniq'); +unlike($dump, qr!(# configuration file $d/map.conf:).*\1!s, 'map.conf uniq'); + is(getconf($t, $dump, 'nginx.conf'), $t->read_file('nginx.conf'), 'content'); is(getconf($t, $dump, 'inc.conf'), $t->read_file('inc.conf'), 'content inc'); is(getconf($t, $dump, 'map.conf'), $t->read_file('map.conf'), 'content inc 2'); @@ -104,3 +108,5 @@ sub getconf { $s =~ tr/\r//d; return $s; } + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/dav.t b/tests/nginx-tests/nginx-tests/dav.t index 52a66522fa..751ea2a00c 100644 --- a/tests/nginx-tests/nginx-tests/dav.t +++ b/tests/nginx-tests/nginx-tests/dav.t @@ -21,7 +21,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http dav/)->plan(20); +my $t = Test::Nginx->new()->has(qw/http dav/)->plan(28); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -39,6 +39,8 @@ http { listen 127.0.0.1:8080; server_name localhost; + absolute_redirect off; + location / { dav_methods PUT DELETE MKCOL COPY MOVE; } @@ -106,6 +108,21 @@ like($r, qr/201 Created.*(Content-Length|\x0d\0a0\x0d\x0a)/ms, is(-s $t->testdir() . '/file', 10, 'put file extra data size'); +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +$r = http(<testdir() . '/file-moved escape', 10, 'file copied unescaped'); $t->write_file('file.exist', join '', (1 .. 42)); @@ -160,14 +183,8 @@ Connection: close EOF like($r, qr/204 No Content/, 'copy file overwrite'); - -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.15.3'); - is(-s $t->testdir() . '/file.exist', 10, 'target file truncated'); -} - $r = http(<testdir() . '/alias', 10, 'put alias size'); +# request methods with unsupported request body + +$r = http(<new()->has(qw/http --with-debug ipv6 proxy/); +my $t = Test::Nginx->new()->has(qw/http --with-debug proxy/); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -41,11 +41,11 @@ http { server { listen 127.0.0.1:8080; - listen [::1]:8080; + listen [::1]:%%PORT_8080%%; server_name localhost; location /debug { - proxy_pass http://[::1]:8080/; + proxy_pass http://[::1]:%%PORT_8080%%/; } } } @@ -60,7 +60,7 @@ http_get('/'); select undef, undef, undef, 0.1; is($t->read_file('debug1.log'), '', 'no debug_connection file 1'); -is($t->read_file('debug2.log'), '', 'no debug_connection file 1'); +is($t->read_file('debug2.log'), '', 'no debug_connection file 2'); http_get('/debug'); diff --git a/tests/nginx-tests/nginx-tests/debug_connection_unix.t b/tests/nginx-tests/nginx-tests/debug_connection_unix.t new file mode 100644 index 0000000000..71082e007a --- /dev/null +++ b/tests/nginx-tests/nginx-tests/debug_connection_unix.t @@ -0,0 +1,73 @@ +#!/usr/bin/perl + +# (C) Nginx, Inc. + +# Tests for debug_connection with unix socket. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http --with-debug unix proxy/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { + debug_connection unix:; +} + +http { + %%TEST_GLOBALS_HTTP%% + + error_log %%TESTDIR%%/debug1.log alert; + error_log %%TESTDIR%%/debug2.log alert; + + server { + listen 127.0.0.1:8080; + listen unix:%%TESTDIR%%/unix.sock; + server_name localhost; + + location /debug { + proxy_pass http://unix:%%TESTDIR%%/unix.sock:/; + } + } +} + +EOF + +$t->run()->plan(5); + +############################################################################### + +http_get('/'); + +select undef, undef, undef, 0.1; +is($t->read_file('debug1.log'), '', 'no debug_connection file 1'); +is($t->read_file('debug2.log'), '', 'no debug_connection file 2'); + +http_get('/debug'); + +select undef, undef, undef, 0.1; +like($t->read_file('debug1.log'), qr/\[debug\]/, 'debug_connection file 1'); +like($t->read_file('debug2.log'), qr/\[debug\]/, 'debug_connection file 2'); +is($t->read_file('debug1.log'), $t->read_file('debug2.log'), + 'debug_connection file1 file2 match'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/fastcgi.t b/tests/nginx-tests/nginx-tests/fastcgi.t index e7b77f1775..9103311377 100644 --- a/tests/nginx-tests/nginx-tests/fastcgi.t +++ b/tests/nginx-tests/nginx-tests/fastcgi.t @@ -25,7 +25,7 @@ eval { require FCGI; }; plan(skip_all => 'FCGI not installed') if $@; plan(skip_all => 'win32') if $^O eq 'MSWin32'; -my $t = Test::Nginx->new()->has(qw/http fastcgi/)->plan(7) +my $t = Test::Nginx->new()->has(qw/http fastcgi/)->plan(8) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -51,6 +51,12 @@ http { fastcgi_param REQUEST_URI $request_uri; } + location /catch { + fastcgi_pass 127.0.0.1:8081; + fastcgi_param REQUEST_URI "/stderr"; + fastcgi_catch_stderr sample; + } + location /var { fastcgi_pass $arg_b; fastcgi_param REQUEST_URI $request_uri; @@ -72,6 +78,7 @@ like(http_get('/'), qr/^3$/m, 'fastcgi third request'); unlike(http_head('/'), qr/SEE-THIS/, 'no data in HEAD'); like(http_get('/stderr'), qr/SEE-THIS/, 'large stderr handled'); +like(http_get('/catch'), qr/502 Bad/, 'catch stderr'); like(http_get('/var?b=127.0.0.1:' . port(8081)), qr/SEE-THIS/, 'fastcgi with variables'); diff --git a/tests/nginx-tests/nginx-tests/fastcgi_body2.t b/tests/nginx-tests/nginx-tests/fastcgi_body2.t index 06216d5d1c..65e927dd9a 100644 --- a/tests/nginx-tests/nginx-tests/fastcgi_body2.t +++ b/tests/nginx-tests/nginx-tests/fastcgi_body2.t @@ -114,7 +114,7 @@ sub fastcgi_daemon { $socket); while( $request->Accept() >= 0 ) { - read(STDIN, my $body, $ENV{'CONTENT_LENGTH'}); + read(STDIN, my $body, $ENV{'CONTENT_LENGTH'} || 0); my $len = length $body; sleep 3 if $port == port(8081); diff --git a/tests/nginx-tests/nginx-tests/fastcgi_extra_data.t b/tests/nginx-tests/nginx-tests/fastcgi_extra_data.t new file mode 100644 index 0000000000..2f35380779 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/fastcgi_extra_data.t @@ -0,0 +1,187 @@ +#!/usr/bin/perl + +# (C) Maxim Dounin +# (C) Nginx, Inc. + +# Test for fastcgi backend, responses with extra data or premature close. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { require FCGI; }; +plan(skip_all => 'FCGI not installed') if $@; +plan(skip_all => 'win32') if $^O eq 'MSWin32'; + +my $t = Test::Nginx->new() + ->has(qw/http fastcgi cache rewrite addition/)->plan(22) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + fastcgi_param REQUEST_URI $request_uri; + fastcgi_param REQUEST_METHOD $request_method; + + fastcgi_cache_path cache keys_zone=one:1m; + fastcgi_cache_key $request_uri; + fastcgi_cache_valid any 1m; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + fastcgi_pass 127.0.0.1:8081; + add_after_body /after; + } + + location /unbuf/ { + fastcgi_pass 127.0.0.1:8081; + fastcgi_buffering off; + add_after_body /after; + } + + location /head/ { + fastcgi_pass 127.0.0.1:8081; + fastcgi_cache one; + add_after_body /after; + } + + location /after { + return 200 ":after\n"; + } + } +} + +EOF + +$t->run_daemon(\&fastcgi_daemon); +$t->run()->waitforsocket('127.0.0.1:' . port(8081)); + +############################################################################### + +like(http_get('/'), qr/SEE-THIS(?!-BUT-NOT-THIS)/, 'response with extra data'); +like(http_get('/short'), qr/SEE-THIS(?!.*:after)/s, 'too short response'); +like(http_get('/empty'), qr/200 OK(?!.*:after)/s, 'empty too short response'); + +like(http_head('/'), qr/200 OK(?!.*SEE-THIS)/s, 'no data in HEAD'); +like(http_head('/short'), qr/200 OK(?!.*SEE-THIS)/s, 'too short to HEAD'); +like(http_head('/empty'), qr/200 OK/, 'empty response to HEAD'); + +# unbuffered responses + +like(http_get('/unbuf/'), qr/SEE-THIS(?!-BUT-NOT-THIS)/, + 'unbuffered with extra data'); +like(http_get('/unbuf/short'), qr/SEE-THIS(?!.*:after)/s, + 'unbuffered too short response'); +like(http_get('/unbuf/empty'), qr/200 OK(?!.*:after)/s, + 'unbuffered empty too short response'); + +like(http_head('/unbuf/'), qr/200 OK(?!.*SEE-THIS)/s, + 'unbuffered no data in HEAD'); +like(http_head('/unbuf/short'), qr/200 OK(?!.*SEE-THIS)/s, + 'unbuffered too short response to HEAD'); +like(http_head('/unbuf/empty'), qr/200 OK/, + 'unbuffered empty response to HEAD'); + +# caching of responsses to HEAD requests + +like(http_head('/head/empty'), qr/200 OK(?!.*SEE-THIS)/s, 'head no body'); +like(http_head('/head/matching'), qr/200 OK(?!.*SEE-THIS)/s, 'head matching'); +like(http_head('/head/extra'), qr/200 OK(?!.*SEE-THIS)/s, 'head extra'); +like(http_head('/head/short'), qr/200 OK(?!.*SEE-THIS)/s, 'head too short'); + +like(http_get('/head/empty'), qr/200 OK/, 'head no body cached'); +like(http_get('/head/matching'), qr/SEE-THIS/, 'head matching cached'); +like(http_get('/head/extra'), qr/SEE-THIS(?!-BUT-NOT-THIS)/s, + 'head extra cached'); +like(http_get('/head/short'), qr/SEE-THIS(?!.*:after)/s, + 'head too short cached'); + +# "zero size buf" alerts (ticket #2018) + +like(http_get('/zero'), qr/200 OK(?!.*NOT-THIS)/s, 'zero size'); +like(http_get('/unbuf/zero'), qr/200 OK(?!.*NOT-THIS)/s, + 'unbuffered zero size'); + +############################################################################### + +sub fastcgi_daemon { + my $socket = FCGI::OpenSocket('127.0.0.1:' . port(8081), 5); + my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, + $socket); + + my ($uri, $head); + + while( $request->Accept() >= 0 ) { + $uri = $ENV{REQUEST_URI}; + $uri =~ s!^/unbuf!!; + + $head = $ENV{REQUEST_METHOD} eq 'HEAD'; + + if ($uri eq '/') { + print "Content-Type: text/html\n"; + print "Content-Length: 8\n\n"; + print "SEE-THIS-BUT-NOT-THIS\n"; + + } elsif ($uri eq '/zero') { + print "Content-Type: text/html\n"; + print "Content-Length: 0\n\n"; + print "NOT-THIS\n"; + + } elsif ($uri eq '/short') { + print "Content-Type: text/html\n"; + print "Content-Length: 100\n\n"; + print "SEE-THIS-TOO-SHORT-RESPONSE\n"; + + } elsif ($uri eq '/empty') { + print "Content-Type: text/html\n"; + print "Content-Length: 100\n\n"; + + } elsif ($uri eq '/head/empty') { + print "Content-Type: text/html\n"; + print "Content-Length: 8\n\n"; + print "SEE-THIS" unless $head; + + } elsif ($uri eq '/head/matching') { + print "Content-Type: text/html\n"; + print "Content-Length: 8\n\n"; + print "SEE-THIS"; + + } elsif ($uri eq '/head/extra') { + print "Content-Type: text/html\n"; + print "Content-Length: 8\n\n"; + print "SEE-THIS-BUT-NOT-THIS\n"; + + } elsif ($uri eq '/head/short') { + print "Content-Type: text/html\n"; + print "Content-Length: 100\n\n"; + print "SEE-THIS\n"; + } + } + + FCGI::CloseSocket($socket); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/fastcgi_header_params.t b/tests/nginx-tests/nginx-tests/fastcgi_header_params.t index 1ebf700bba..e7a4182690 100644 --- a/tests/nginx-tests/nginx-tests/fastcgi_header_params.t +++ b/tests/nginx-tests/nginx-tests/fastcgi_header_params.t @@ -25,7 +25,7 @@ eval { require FCGI; }; plan(skip_all => 'FCGI not installed') if $@; plan(skip_all => 'win32') if $^O eq 'MSWin32'; -my $t = Test::Nginx->new()->has(qw/http fastcgi/)->plan(1) +my $t = Test::Nginx->new()->has(qw/http fastcgi/)->plan(4) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -59,6 +59,37 @@ $t->run()->waitforsocket('127.0.0.1:' . port(8081)); like(http_get_headers('/'), qr/SEE-THIS/, 'fastcgi request with many ignored headers'); +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +my $r; + +$r = http(<Accept() >= 0 ) { $count++; + my $xfwd = $ENV{HTTP_X_FORWARDED_FOR} || ''; + my $cookie = $ENV{HTTP_COOKIE} || ''; + my $foo = $ENV{HTTP_FOO} || ''; + print <Accept() >= 0 ) { $count++; - my $ims = $ENV{HTTP_IF_MODIFIED_SINCE}; - my $iums = $ENV{HTTP_IF_UNMODIFIED_SINCE}; - my $blah = $ENV{HTTP_X_BLAH}; + my $ims = $ENV{HTTP_IF_MODIFIED_SINCE} || ''; + my $iums = $ENV{HTTP_IF_UNMODIFIED_SINCE} || ''; + my $blah = $ENV{HTTP_X_BLAH} || ''; print <Accept() >= 0 ) { $count++; - my $ims = $ENV{HTTP_IF_MODIFIED_SINCE}; - my $iums = $ENV{HTTP_IF_UNMODIFIED_SINCE}; - my $blah = $ENV{HTTP_X_BLAH}; + my $ims = $ENV{HTTP_IF_MODIFIED_SINCE} || ''; + my $iums = $ENV{HTTP_IF_UNMODIFIED_SINCE} || ''; + my $blah = $ENV{HTTP_X_BLAH} || ''; print <Accept() >= 0 ) { $count++; - read(STDIN, $body, $ENV{'CONTENT_LENGTH'}); + read(STDIN, $body, $ENV{'CONTENT_LENGTH'} || 0); if ($ENV{REQUEST_URI} eq '/error_page') { print "Status: 404 Not Found" . CRLF . CRLF; diff --git a/tests/nginx-tests/nginx-tests/geo_unix.t b/tests/nginx-tests/nginx-tests/geo_unix.t index c89d9d8b85..fcdace4005 100644 --- a/tests/nginx-tests/nginx-tests/geo_unix.t +++ b/tests/nginx-tests/nginx-tests/geo_unix.t @@ -22,7 +22,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http geo proxy unix/)->plan(5); +my $t = Test::Nginx->new()->has(qw/http geo proxy unix/)->plan(6); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -93,18 +93,15 @@ $t->run(); ############################################################################### my $r = http_get('/'); - -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.15.8'); - like($r, qr/^X-Geo: none/m, 'geo unix'); like($r, qr/^X-Ranges: none/m, 'geo unix ranges'); - -} - like($r, qr/^X-Addr: none/m, 'geo unix remote addr'); like($r, qr/^X-Ranges-Addr: none/m, 'geo unix ranges remote addr'); like(http_get('/?ip=192.0.2.1'), qr/^X-Arg: test/m, 'geo unix variable'); +$t->stop(); + +is(-e $t->testdir() . '/unix.sock', undef, 'unix socket removed'); + ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/grpc.t b/tests/nginx-tests/nginx-tests/grpc.t index 9ddd01060d..22eeedbc77 100644 --- a/tests/nginx-tests/nginx-tests/grpc.t +++ b/tests/nginx-tests/nginx-tests/grpc.t @@ -24,7 +24,7 @@ select STDERR; $| = 1; select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/http rewrite http_v2 grpc/) - ->has(qw/upstream_keepalive/); + ->has(qw/upstream_keepalive/)->plan(146); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -47,9 +47,8 @@ http { listen 127.0.0.1:8080 http2; server_name localhost; - http2_max_field_size 128k; - http2_max_header_size 128k; http2_body_preread_size 128k; + large_client_header_buffers 4 32k; location / { grpc_pass grpc://127.0.0.1:8081; @@ -91,7 +90,7 @@ http { EOF -$t->try_run('no grpc')->plan(100); +$t->run(); ############################################################################### @@ -155,6 +154,23 @@ $frames = $f->{http_end}(); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; cmp_ok($frame->{headers}{'x-connection'}, '>', $c, 'response 2 - connection'); +# request body - special last buffer + +$f->{http_start}('/SayHello'); +$frames = $f->{data}('Hello', body_more => 1); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame->{data}, 'Hello', 'request body first - DATA'); +is($frame->{length}, 5, 'request body first - DATA length'); +is($frame->{flags}, 0, 'request body first - DATA flags'); +$frames = $f->{data}(''); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame->{data}, '', 'special buffer last - DATA'); +is($frame->{length}, 0, 'special buffer last - DATA length'); +is($frame->{flags}, 1, 'special buffer last - DATA flags'); +$frames = $f->{http_end}(); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}{':status'}, '200', 'special buffer last - response'); + # upstream keepalive $frames = $f->{http_start}('/KeepAlive'); @@ -173,6 +189,124 @@ $frames = $f->{http_end}(); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; is($frame->{headers}{'x-connection'}, $c, 'keepalive - connection reuse'); +# upstream keepalive +# pending control frame ack after the response + +undef $f; +$f = grpc(); + +$frames = $f->{http_start}('/KeepAlive'); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{sid}, $sid, 'keepalive 2 - HEADERS sid'); +$f->{data}('Hello'); +$f->{settings}(0, 1 => 4096); +$frames = $f->{http_end}(); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +ok($c = $frame->{headers}{'x-connection'}, 'keepalive 2 - connection'); + +$frames = $f->{http_start}('/KeepAlive', reuse => 1); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +ok($frame, 'upstream keepalive reused'); + +cmp_ok($frame->{sid}, '>', $sid, 'keepalive 2 - HEADERS sid next'); +$f->{data}('Hello'); +$frames = $f->{http_end}(); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}{'x-connection'}, $c, 'keepalive 2 - connection reuse'); + +undef $f; +$f = grpc(); + +# upstream keepalive +# grpc filter setting INITIAL_WINDOW_SIZE is inherited in the next stream + +$f->{http_start}('/KeepAlive'); +$f->{data}('Hello'); +$f->{settings}(0, 1 => 4096); +$frames = $f->{http_end}(grpc_filter_settings => { 0x4 => 2 }); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +ok($c = $frame->{headers}{'x-connection'}, 'keepalive 3 - connection'); + +$f->{http_start}('/KeepAlive', reuse => 1); +$frames = $f->{data_len}('Hello', 2); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame->{data}, 'He', 'grpc filter setting - DATA'); +is($frame->{length}, 2, 'grpc filter setting - DATA length'); +is($frame->{flags}, 0, 'grpc filter setting - DATA flags'); +$f->{settings}(0, 0x4 => 5); +$frames = $f->{data_len}(undef, 3); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame->{data}, 'llo', 'setting updated - DATA'); +is($frame->{length}, 3, 'setting updated - DATA length'); +is($frame->{flags}, 1, 'setting updated - DATA flags'); +$frames = $f->{http_end}(); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}{'x-connection'}, $c, 'keepalive 3 - connection reuse'); + +undef $f; +$f = grpc(); + +# upstream keepalive - GOAWAY, current request aborted + +$f->{http_start}('/KeepAlive'); +$f->{data}('Hello'); +$frames = $f->{http_end}(); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +ok($c = $frame->{headers}{'x-connection'}, 'keepalive 4 - connection'); + +$f->{http_start}('/KeepAlive', reuse => 1); +$f->{goaway}(0, 0, 5); +$f->{data}('Hello'); +$frames = $f->{http_end}(); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}{':status'}, 502, 'keepalive 4 - GOAWAY aborted request'); + +$f->{http_start}('/KeepAlive'); +$f->{data}('Hello'); +$frames = $f->{http_end}(); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +cmp_ok($frame->{headers}{'x-connection'}, '>', $c, 'keepalive 4 - closed'); + +undef $f; +$f = grpc(); + +# upstream keepalive - disabled with a higher GOAWAY Last-Stream-ID + +$f->{http_start}('/KeepAlive'); +$f->{goaway}(0, 3, 5); +$f->{data}('Hello'); +$frames = $f->{http_end}(); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +ok($c = $frame->{headers}{'x-connection'}, 'keepalive 5 - GOAWAY next stream'); + +$f->{http_start}('/KeepAlive'); +$f->{data}('Hello'); +$frames = $f->{http_end}(); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +cmp_ok($frame->{headers}{'x-connection'}, '>', $c, 'keepalive 5 - closed'); + +undef $f; +$f = grpc(); + +# upstream keepalive - GOAWAY in grpc filter, current stream aborted + +$f->{http_start}('/KeepAlive'); +$f->{data}('Hello'); +$frames = $f->{http_end}(grpc_filter_goaway => [0, 0, 5]); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +ok($c = $frame->{headers}{'x-connection'}, 'keepalive 6 - connection'); +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; +ok($frame, 'keepalive 6 - grpc filter GOAWAY aborted stream'); + +$f->{http_start}('/KeepAlive'); +$f->{data}('Hello'); +$frames = $f->{http_end}(); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +cmp_ok($frame->{headers}{'x-connection'}, '>', $c, 'keepalive 6 - closed'); + +undef $f; +$f = grpc(); + # various header compression formats $f->{http_start}('/SayHello'); @@ -249,6 +383,20 @@ is($frame->{flags}, 5, 'grpc error - HEADERS flags'); ($frame) = grep { $_->{type} eq "DATA" } @$frames; ok(!$frame, 'grpc error - no DATA frame'); +# malformed response body length not equal to content-length + +$f->{http_start}('/SayHello'); +$f->{data}('Hello'); +$frames = $f->{http_err2}(cl => 42); +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; +ok($frame, 'response body less than content-length'); + +$f->{http_start}('/SayHello'); +$f->{data}('Hello'); +$frames = $f->{http_err2}(cl => 8); +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; +ok($frame, 'response body more than content-length'); + # continuation from backend, expect parts assembled $f->{http_start}('/SayHello'); @@ -331,6 +479,63 @@ $frames = $f->{field_len}(2**15); ($frame) = grep { $_->{flags} & 0x4 } @$frames; is($frame->{headers}{'x' x 2**15}, 'y' x 2**15, 'long header field 3'); +# Intermediary Encapsulation Attacks, malformed header fields + +$f->{http_start}('/'); +$f->{data}('Hello'); +$frames = $f->{field_bad}(n => 'n:n'); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}{':status'}, 502, 'invalid header name colon'); + +$f->{http_start}('/'); +$f->{data}('Hello'); +$frames = $f->{field_bad}(n => 'NN'); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}{':status'}, 502, 'invalid header name uppercase'); + +$f->{http_start}('/'); +$f->{data}('Hello'); +$frames = $f->{field_bad}(n => "n\nn"); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}{':status'}, 502, 'invalid header name ctl'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.1'); + +$f->{http_start}('/'); +$f->{data}('Hello'); +$frames = $f->{field_bad}(n => "n n"); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}{':status'}, 502, 'invalid header name space'); + +} + +$f->{http_start}('/'); +$f->{data}('Hello'); +$frames = $f->{field_bad}(v => "v\nv"); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}{':status'}, 502, 'invalid header value ctl'); + +# invalid HPACK index + +$f->{http_start}('/'); +$f->{data}('Hello'); +$frames = $f->{field_bad}('m' => 0); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}{':status'}, 502, 'invalid index - indexed header'); + +$f->{http_start}('/'); +$f->{data}('Hello'); +$frames = $f->{field_bad}('m' => 1); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}{':status'}, 502, 'invalid index - with indexing'); + +$f->{http_start}('/'); +$f->{data}('Hello'); +$frames = $f->{field_bad}('m' => 3); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}{':status'}, 502, 'invalid index - without indexing'); + # flow control $f->{http_start}('/FlowControl'); @@ -377,9 +582,19 @@ $f->{http_start}('/SayPadding'); $f->{data}('Hello'); $frames = $f->{http_end}(body_padding => 42); ($frame) = grep { $_->{type} eq "DATA" } @$frames; -is($frame->{data}, 'Hello world', 'response - DATA'); -is($frame->{length}, 11, 'response - DATA length'); -is($frame->{flags}, 0, 'response - DATA flags'); +is($frame->{data}, 'Hello world', 'DATA padding'); +is($frame->{length}, 11, 'DATA padding - length'); +is($frame->{flags}, 0, 'DATA padding - flags'); + +# DATA padding with Content-Length + +$f->{http_start}('/SayPadding'); +$f->{data}('Hello'); +$frames = $f->{http_end}(body_padding => 42, cl => length('Hello world')); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame->{data}, 'Hello world', 'DATA padding cl'); +is($frame->{length}, 11, 'DATA padding cl - length'); +is($frame->{flags}, 0, 'DATA padding cl - flags'); # :authority inheritance @@ -410,12 +625,6 @@ is($frame->{headers}{':path'}, '/SetArgs?1', 'set args len'); $f->{data}('Hello'); $f->{http_end}(); -$frames = $f->{http_start}('/SetArgs esc'); -($frame) = grep { $_->{type} eq "HEADERS" } @$frames; -is($frame->{headers}{':path'}, '/SetArgs%20esc', 'uri escape'); -$f->{data}('Hello'); -$f->{http_end}(); - $frames = $f->{http_start}('/'); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; is($frame->{headers}{':path'}, '/', 'root index'); @@ -434,6 +643,53 @@ is($frame->{headers}{':method'}, 'HEAD', 'method head'); $f->{data}('Hello'); $f->{http_end}(); +# receiving END_STREAM followed by WINDOW_UPDATE on incomplete request body + +$f->{http_start}('/Discard_WU'); +$frames = $f->{discard}(); +(undef, $frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{flags}, 5, 'discard WINDOW_UPDATE - trailers'); + +# receiving END_STREAM followed by RST_STREAM NO_ERROR + +$f->{http_start}('/Discard_NE'); +$frames = $f->{discard}(); +(undef, $frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{flags}, 5, 'discard NO_ERROR - trailers'); + +# receiving END_STREAM followed by several RST_STREAM NO_ERROR + +$f->{http_start}('/Discard_NE3'); +$frames = $f->{discard}(); +(undef, $frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{flags}, undef, 'discard NO_ERROR many - no trailers'); + +# receiving END_STREAM followed by RST_STREAM CANCEL + +$f->{http_start}('/Discard_CNL'); +$frames = $f->{discard}(); +(undef, $frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{flags}, undef, 'discard CANCEL - no trailers'); + +undef $f; +$f = grpc(); + +# upstream keepalive, grpc error +# receiving END_STREAM followed by RST_STREAM NO_ERROR + +$f->{http_start}('/KeepAlive'); +$f->{data}('Hello'); +$frames = $f->{http_err_rst}(); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +ok($frame->{headers}{'grpc-status'}, 'keepalive 3 - grpc error, rst'); + +$frames = $f->{http_start}('/KeepAlive', reuse => 1); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +ok($frame, 'keepalive 3 - connection reused'); + +undef $f; +$f = grpc(); + ############################################################################### sub grpc { @@ -463,17 +719,13 @@ sub grpc { { name => 'te', value => 'trailers', mode => 2 }]}); if (!$extra{reuse}) { - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - alarm(5); - - $client = $server->accept() or return; + if (IO::Select->new($server)->can_read(5)) { + $client = $server->accept(); - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + } else { + log_in("timeout"); + # connection could be unexpectedly reused + goto reused if $client; return undef; } @@ -486,6 +738,7 @@ sub grpc { pure => 1, preface => "") or return; } +reused: my $frames = $c->read(all => [{ fin => 4 }]); if (!$extra{reuse}) { @@ -514,18 +767,30 @@ sub grpc { $f->{update_sid} = sub { $c->h2_window(shift, $sid); }; + $f->{settings} = sub { + $c->h2_settings(@_); + }; + $f->{goaway} = sub { + $c->h2_goaway(@_); + }; $f->{http_end} = sub { my (%extra) = @_; - $c->new_stream({ body_more => 1, %extra, headers => [ + my $h = [ { name => ':status', value => '200', mode => $extra{mode} || 0 }, { name => 'content-type', value => 'application/grpc', mode => $extra{mode} || 1, huff => 1 }, { name => 'x-connection', value => $n, - mode => 2, huff => 1 }, - ]}, $sid); + mode => 2, huff => 1 }]; + push @$h, { name => 'content-length', value => $extra{cl} } + if $extra{cl}; + $c->new_stream({ body_more => 1, headers => $h, %extra }, $sid); $c->h2_body('Hello world', { body_more => 1, body_padding => $extra{body_padding} }); + $c->h2_settings(0, %{$extra{grpc_filter_settings}}) + if $extra{grpc_filter_settings}; + $c->h2_goaway(@{$extra{grpc_filter_goaway}}) + if $extra{grpc_filter_goaway}; $c->new_stream({ headers => [ { name => 'grpc-status', value => '0', mode => 2, huff => 1 }, @@ -533,6 +798,8 @@ sub grpc { mode => 2, huff => 1 }, ]}, $sid); + return $s->read(all => [{ type => 'RST_STREAM' }]) + if $extra{grpc_filter_goaway}; return $s->read(all => [{ fin => 1 }]); }; $f->{http_pres} = sub { @@ -577,6 +844,40 @@ sub grpc { return $s->read(all => [{ fin => 1 }]); }; + $f->{http_err_rst} = sub { + $c->start_chain(); + $c->new_stream({ headers => [ + { name => ':status', value => '200', mode => 0 }, + { name => 'content-type', value => 'application/grpc' }, + { name => 'grpc-status', value => '12', mode => 2 }, + { name => 'grpc-message', value => 'unknown service', + mode => 2 }, + ]}, $sid); + $c->h2_rst($sid, 0); + $c->send_chain(); + + return $s->read(all => [{ fin => 1 }]); + }; + $f->{http_err2} = sub { + my %extra = @_; + $c->new_stream({ body_more => 1, headers => [ + { name => ':status', value => '200', mode => 0 }, + { name => 'content-type', value => 'application/grpc', + mode => 1, huff => 1 }, + { name => 'content-length', value => $extra{cl}, + mode => 1, huff => 1 }, + ]}, $sid); + $c->h2_body('Hello world', + { body_more => 1, body_split => [5] }); + $c->new_stream({ headers => [ + { name => 'grpc-status', value => '0', + mode => 2, huff => 1 }, + { name => 'grpc-message', value => '', + mode => 2, huff => 1 }, + ]}, $sid); + + return $s->read(all => [{ type => 'RST_STREAM' }]); + }; $f->{continuation} = sub { $c->new_stream({ continuation => 1, body_more => 1, headers => [ { name => ':status', value => '200', mode => 0 }, @@ -619,6 +920,48 @@ sub grpc { return $s->read(all => [{ fin => 1 }]); }; + $f->{field_bad} = sub { + my (%extra) = @_; + my $n = defined $extra{'n'} ? $extra{'n'} : 'n'; + my $v = defined $extra{'v'} ? $extra{'v'} : 'v'; + my $m = defined $extra{'m'} ? $extra{'m'} : 2; + $c->new_stream({ headers => [ + { name => ':status', value => '200' }, + { name => $n, value => $v, mode => $m }, + ]}, $sid); + + return $s->read(all => [{ fin => 1 }]); + }; + $f->{discard} = sub { + my (%extra) = @_; + $c->new_stream({ body_more => 1, %extra, headers => [ + { name => ':status', value => '200', + mode => $extra{mode} || 0 }, + { name => 'content-type', value => 'application/grpc', + mode => $extra{mode} || 1, huff => 1 }, + { name => 'x-connection', value => $n, + mode => 2, huff => 1 }, + ]}, $sid); + $c->h2_body('Hello world', { body_more => 1, + body_padding => $extra{body_padding} }); + + # stick trailers and subsequent frames for reproducibility + + $c->start_chain(); + $c->new_stream({ headers => [ + { name => 'grpc-status', value => '0', mode => 2 } + ]}, $sid); + $c->h2_window(42, $sid) if $uri eq '/Discard_WU'; + $c->h2_rst($sid, 0) if $uri eq '/Discard_NE'; + $c->h2_rst($sid, 0), $c->h2_rst($sid, 0), $c->h2_rst($sid, 0) + if $uri eq '/Discard_NE3'; + $c->h2_rst($sid, 8) if $uri eq '/Discard_CNL'; + $c->send_chain(); + + return $s->read(all => [{ fin => 1 }], wait => 2) + if $uri eq '/Discard_WU' || $uri eq '/Discard_NE'; + return $s->read(all => [{ type => 'RST_STREAM' }]); + }; return $f; } diff --git a/tests/nginx-tests/nginx-tests/grpc_next_upstream.t b/tests/nginx-tests/nginx-tests/grpc_next_upstream.t index 6dca870ab9..8a2a2497cf 100644 --- a/tests/nginx-tests/nginx-tests/grpc_next_upstream.t +++ b/tests/nginx-tests/nginx-tests/grpc_next_upstream.t @@ -23,7 +23,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http http_v2 grpc rewrite/); +my $t = Test::Nginx->new()->has(qw/http http_v2 grpc rewrite/)->plan(9); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -107,7 +107,7 @@ http { EOF -$t->try_run('no grpc')->plan(9); +$t->run(); ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/grpc_pass.t b/tests/nginx-tests/nginx-tests/grpc_pass.t new file mode 100644 index 0000000000..17130f88a7 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/grpc_pass.t @@ -0,0 +1,192 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for the grpc_pass directive with variables. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_ssl http_v2 grpc rewrite/) + ->has_daemon('openssl')->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + upstream u { + server 127.0.0.1:8081; + } + + resolver 127.0.0.1:%%PORT_8982_UDP%%; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + grpc_pass $host:%%PORT_8081%%; + } + + location /grpc { + grpc_pass grpc://$host:%%PORT_8081%%; + } + + location /grpcs { + grpc_pass grpcs://$host:%%PORT_8082%%; + } + + location /arg { + grpc_pass $arg_b; + } + } + + server { + listen 127.0.0.1:8081 http2; + listen 127.0.0.1:8082 http2 ssl; + server_name localhost; + + ssl_certificate_key localhost.key; + ssl_certificate localhost.crt; + + location / { + return 200 $http_host; + } + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('localhost') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->run_daemon(\&dns_daemon, port(8982), $t); +$t->run()->plan(5); + +$t->waitforfile($t->testdir . '/' . port(8982)); + +############################################################################### + +like(http_get('/basic'), qr/200 OK/, 'no scheme'); +like(http_get('/grpc'), qr/200 OK/, 'grpc scheme'); + +SKIP: { +$t->{_configure_args} =~ /OpenSSL ([\d\.]+)/; +skip 'OpenSSL too old', 1 unless defined $1 and $1 ge '1.0.2'; + +like(http_get('/grpcs'), qr/200 OK/, 'grpcs scheme'); + +} + +like(http_get('/arg?b=grpc://127.0.0.1:' . port(8081)), qr/200 OK/, 'addrs'); +like(http_get('/arg?b=grpc://u'), qr/200 OK/, 'no_port'); + +############################################################################### + +sub reply_handler { + my ($recv_data) = @_; + + my (@name, @rdata); + + use constant NOERROR => 0; + use constant A => 1; + use constant IN => 1; + + # default values + + my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 3600); + + # decode name + + my ($len, $offset) = (undef, 12); + while (1) { + $len = unpack("\@$offset C", $recv_data); + last if $len == 0; + $offset++; + push @name, unpack("\@$offset A$len", $recv_data); + $offset += $len; + } + + $offset -= 1; + my ($id, $type, $class) = unpack("n x$offset n2", $recv_data); + + my $name = join('.', @name); + if ($name eq 'localhost' && $type == A) { + push @rdata, rd_addr($ttl, '127.0.0.1'); + } + + $len = @name; + pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata, + 0, 0, @name, $type, $class) . join('', @rdata); +} + +sub rd_addr { + my ($ttl, $addr) = @_; + + my $code = 'split(/\./, $addr)'; + + return pack 'n3N', 0xc00c, A, IN, $ttl if $addr eq ''; + + pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code); +} + +sub dns_daemon { + my ($port, $t) = @_; + + my ($data, $recv_data); + my $socket = IO::Socket::INET->new( + LocalAddr => '127.0.0.1', + LocalPort => $port, + Proto => 'udp', + ) + or die "Can't create listening socket: $!\n"; + + # signal we are ready + + open my $fh, '>', $t->testdir() . '/' . $port; + close $fh; + + while (1) { + $socket->recv($recv_data, 65536); + $data = reply_handler($recv_data); + $socket->send($data); + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/grpc_request_buffering.t b/tests/nginx-tests/nginx-tests/grpc_request_buffering.t index 26b394c181..3cb7021bb9 100644 --- a/tests/nginx-tests/nginx-tests/grpc_request_buffering.t +++ b/tests/nginx-tests/nginx-tests/grpc_request_buffering.t @@ -23,7 +23,7 @@ use Test::Nginx::HTTP2; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http http_v2 grpc mirror/); +my $t = Test::Nginx->new()->has(qw/http http_v2 grpc mirror proxy/)->plan(12); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -39,6 +39,7 @@ http { server { listen 127.0.0.1:8080 http2; + listen 127.0.0.1:8082; server_name localhost; location /mirror { } @@ -48,12 +49,22 @@ http { add_header X-Body $request_body; mirror /mirror; } + + location /proxy { + proxy_pass http://127.0.0.1:8082/mirror; + proxy_intercept_errors on; + error_page 404 = @fallback; + } + + location @fallback { + grpc_pass 127.0.0.1:8081; + } } } EOF -$t->try_run('no grpc')->plan(9); +$t->run(); ############################################################################### @@ -77,6 +88,23 @@ $frames = $f->{http_end}(); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; is($frame->{headers}{'x-body'}, 'Hello', 'request body in memory'); +# tcp_nopush usage on peer connections +# reopen window for request body after initial window was exhausted + +$frames = $f->{http_start}('/proxy'); +is(eval(join '+', map { $_->{length} } grep { $_->{type} eq "DATA" } @$frames), + 65535, 'preserve_output - first body bytes'); + +# expect body cleanup is disabled with preserve_output (ticket #1565). +# after request body first bytes were proxied on behalf of initial window size, +# send response header from upstream, this leads to body cleanup code path + +$frames = $f->{http_end}(); +is(eval(join '+', map { $_->{length} } grep { $_->{type} eq "DATA" } @$frames), + 465, 'preserve_output - last body bytes'); + +like(`grep -F '[crit]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no crits'); + ############################################################################### sub grpc { @@ -94,12 +122,13 @@ sub grpc { $f->{http_start} = sub { ($uri, my %extra) = @_; $s = Test::Nginx::HTTP2->new() if !defined $s; - $s->new_stream({ body => 'Hello', headers => [ + my ($body) = $uri eq '/proxy' ? 'Hello' x 13200 : 'Hello'; + $s->new_stream({ body => $body, headers => [ { name => ':method', value => 'POST', mode => 0 }, { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => $uri }, { name => ':authority', value => 'localhost' }, - { name => 'content-length', value => '5' }]}); + { name => 'content-length', value => length($body) }]}); if (!$extra{reuse}) { eval { @@ -124,7 +153,9 @@ sub grpc { pure => 1, preface => "") or return; } - my $frames = $c->read(all => [{ fin => 1 }]); + my $frames = $uri eq '/proxy' + ? $c->read(all => [{ length => 65535 }]) + : $c->read(all => [{ fin => 1 }]); if (!$extra{reuse}) { $c->h2_settings(0); @@ -140,6 +171,15 @@ sub grpc { { name => ':status', value => '200', mode => 0 }, { name => 'content-type', value => 'application/grpc' }, ]}, $sid); + + # reopen window for request body after response HEADERS is sent + + if ($uri eq '/proxy') { + $c->h2_window(2**16, $sid); + $c->h2_window(2**16); + return $c->read(all => [{ sid => $sid, fin => 1 }]); + } + $c->h2_body('Hello world', { body_more => 1 }); $c->new_stream({ headers => [ { name => 'grpc-status', value => '0', mode => 2 }, diff --git a/tests/nginx-tests/nginx-tests/grpc_ssl.t b/tests/nginx-tests/nginx-tests/grpc_ssl.t index a5947ccbc2..759dff45cf 100644 --- a/tests/nginx-tests/nginx-tests/grpc_ssl.t +++ b/tests/nginx-tests/nginx-tests/grpc_ssl.t @@ -24,12 +24,12 @@ select STDERR; $| = 1; select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/http rewrite http_v2 grpc/) - ->has(qw/upstream_keepalive http_ssl/); + ->has(qw/upstream_keepalive http_ssl/)->has_daemon('openssl'); $t->{_configure_args} =~ /OpenSSL ([\d\.]+)/; plan(skip_all => 'OpenSSL too old') unless defined $1 and $1 ge '1.0.2'; -$t->write_file_expand('nginx.conf', <<'EOF'); +$t->write_file_expand('nginx.conf', <<'EOF')->plan(38); %%TEST_GLOBALS%% @@ -56,8 +56,6 @@ http { ssl_verify_client optional; ssl_client_certificate client.crt; - http2_max_field_size 128k; - http2_max_header_size 128k; http2_body_preread_size 128k; location / { @@ -70,8 +68,6 @@ http { listen 127.0.0.1:8080 http2; server_name localhost; - http2_max_field_size 128k; - http2_max_header_size 128k; http2_body_preread_size 128k; location / { @@ -135,7 +131,7 @@ sleep 1 if $^O eq 'MSWin32'; $t->write_file('password', 'client'); -$t->try_run('no grpc')->plan(33); +$t->run(); ############################################################################### @@ -197,6 +193,31 @@ $frames = $f->{http_end}(); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; cmp_ok($frame->{headers}{'x-connection'}, '>', $c, 'response 2 - connection'); +# flow control + +$f->{http_start}('/FlowControl'); +$frames = $f->{data_len}(('Hello' x 13000) . ('x' x 550), 65535); +my $sum = eval join '+', map { $_->{type} eq "DATA" && $_->{length} } @$frames; +is($sum, 65535, 'flow control - iws length'); + +$f->{update}(10); +$f->{update_sid}(10); + +$frames = $f->{data_len}(undef, 10); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame->{length}, 10, 'flow control - update length'); +is($frame->{flags}, 0, 'flow control - update flags'); + +$f->{update_sid}(10); +$f->{update}(10); + +$frames = $f->{data_len}(undef, 5); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame->{length}, 5, 'flow control - rest length'); +is($frame->{flags}, 1, 'flow control - rest flags'); + +$f->{http_end}(); + # upstream keepalive $f->{http_start}('/KeepAlive'); @@ -205,17 +226,12 @@ $frames = $f->{http_end}(); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; ok($c = $frame->{headers}{'x-connection'}, 'keepalive - connection'); -TODO: { -local $TODO = 'not yet' if $^O eq 'MSWin32'; - $f->{http_start}('/KeepAlive'); $f->{data}('Hello'); $frames = $f->{http_end}(); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; is($frame->{headers}{'x-connection'}, $c, 'keepalive - connection reuse'); -} - ############################################################################### sub grpc { @@ -276,6 +292,17 @@ sub grpc { $sid = $frame->{sid}; return $frames; }; + $f->{data_len} = sub { + my ($body, $len) = @_; + $s->h2_body($body) if defined $body; + return $c->read(all => [{ sid => $sid, length => $len }]); + }; + $f->{update} = sub { + $c->h2_window(shift); + }; + $f->{update_sid} = sub { + $c->h2_window(shift, $sid); + }; $f->{data} = sub { my ($body, %extra) = @_; $s->h2_body($body, { %extra }); diff --git a/tests/nginx-tests/nginx-tests/h2.t b/tests/nginx-tests/nginx-tests/h2.t index 07dbf2fd0f..1267c05e4a 100644 --- a/tests/nginx-tests/nginx-tests/h2.t +++ b/tests/nginx-tests/nginx-tests/h2.t @@ -26,7 +26,7 @@ select STDERR; $| = 1; select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite charset gzip/) - ->plan(147); + ->plan(144); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -106,33 +106,12 @@ http { http2_max_concurrent_streams 1; } - server { - listen 127.0.0.1:8084 http2; - server_name localhost; - - http2_recv_timeout 1s; - client_header_timeout 1s; - send_timeout 1s; - } - - server { - listen 127.0.0.1:8085 http2; - server_name localhost; - - http2_idle_timeout 1s; - client_body_timeout 1s; - - location /proxy2/ { - add_header X-Body $request_body; - proxy_pass http://127.0.0.1:8081/; - } - } - server { listen 127.0.0.1:8086 http2; server_name localhost; send_timeout 1s; + lingering_close off; } server { @@ -141,6 +120,9 @@ http { client_header_timeout 1s; client_body_timeout 1s; + lingering_close off; + + location / { } location /proxy/ { proxy_pass http://127.0.0.1:8081/; @@ -163,25 +145,6 @@ $t->write_file('t2.html', 'SEE-THIS'); ############################################################################### -# Upgrade mechanism - -my $r = http(<new(port(8080), pure => 1); @@ -244,40 +207,6 @@ is($frame->{value}, 'SEE-THIS', 'PING payload'); is($frame->{flags}, 1, 'PING flags ack'); is($frame->{sid}, 0, 'PING stream'); -# timeouts - -SKIP: { -skip 'long tests', 6 unless $ENV{TEST_NGINX_UNSAFE}; - -push my @s, Test::Nginx::HTTP2->new(port(8084), pure => 1); -push @s, Test::Nginx::HTTP2->new(port(8084), pure => 1); -$s[-1]->h2_ping('SEE-THIS'); -push @s, Test::Nginx::HTTP2->new(port(8085), pure => 1); -push @s, Test::Nginx::HTTP2->new(port(8085), pure => 1); -$s[-1]->h2_ping('SEE-THIS'); - -select undef, undef, undef, 2.1; - -$frames = (shift @s)->read(all => [{ type => "GOAWAY" }]); -($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; -ok($frame, 'recv timeout - new connection GOAWAY'); -is($frame->{code}, 1, 'recv timeout - new connection code'); - -$frames = (shift @s)->read(all => [{ type => "GOAWAY" }]); -($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; -is($frame, undef, 'recv timeout - idle connection GOAWAY'); - -$frames = (shift @s)->read(all => [{ type => "GOAWAY" }]); -($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; -is($frame, undef, 'idle timeout - new connection GOAWAY'); - -$frames = (shift @s)->read(all => [{ type => "GOAWAY" }]); -($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; -ok($frame, 'idle timeout - idle connection GOAWAY'); -is($frame->{code}, 0, 'idle timeout - idle connection code'); - -} - # GOAWAY Test::Nginx::HTTP2->new()->h2_goaway(0, 0, 5); @@ -300,9 +229,6 @@ is($frame->{code}, 6, 'GOAWAY invalid length - GOAWAY FRAME_SIZE_ERROR'); # An endpoint MUST treat a GOAWAY frame with a stream identifier other # than 0x0 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. -TODO: { -local $TODO = 'not yet'; - $s = Test::Nginx::HTTP2->new(); $s->h2_goaway(1, 0, 5, 'foobar'); $frames = $s->read(all => [{ type => "GOAWAY" }], wait => 0.5); @@ -311,8 +237,6 @@ $frames = $s->read(all => [{ type => "GOAWAY" }], wait => 0.5); ok($frame, 'GOAWAY invalid stream - GOAWAY frame'); is($frame->{code}, 1, 'GOAWAY invalid stream - GOAWAY PROTOCOL_ERROR'); -} - # client-initiated PUSH_PROMISE, just to ensure nothing went wrong # N.B. other implementation returns zero code, which is not anyhow regulated @@ -374,6 +298,29 @@ is($frame->{headers}->{'x-header'}, 'X-Foo', 'HEAD - HEADERS header'); ($frame) = grep { $_->{type} eq "DATA" } @$frames; is($frame, undef, 'HEAD - no body'); +# CONNECT + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.1'); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ method => 'CONNECT' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 405, 'CONNECT - not allowed'); + +} + +# TRACE + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ method => 'TRACE' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 405, 'TRACE - not allowed'); + # range filter $s = Test::Nginx::HTTP2->new(); @@ -656,6 +603,23 @@ $frames = $s->read(all => [{ type => 'PING' }]); ($frame) = grep { $_->{type} eq "PING" && $_->{flags} & 0x1 } @$frames; ok($frame, 'client header timeout - PING'); +# partial request header frame received (no field split), +# the rest of frame is received after client header timeout + +$s = Test::Nginx::HTTP2->new(port(8087)); +$sid = $s->new_stream({ path => '/t2.html', split => [20], split_delay => 2.1 }); +$frames = $s->read(all => [{ type => 'RST_STREAM' }]); + +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; +ok($frame, 'client header timeout 2'); +is($frame->{code}, 1, 'client header timeout 2 - protocol error'); + +$s->h2_ping('SEE-THIS'); +$frames = $s->read(all => [{ type => 'PING' }]); + +($frame) = grep { $_->{type} eq "PING" && $_->{flags} & 0x1 } @$frames; +ok($frame, 'client header timeout 2 - PING'); + # partial request body data frame received, the rest is after body timeout $s = Test::Nginx::HTTP2->new(port(8087)); @@ -673,6 +637,15 @@ $frames = $s->read(all => [{ type => 'PING' }]); ($frame) = grep { $_->{type} eq "PING" && $_->{flags} & 0x1 } @$frames; ok($frame, 'client body timeout - PING'); +# partial request body data frame with connection close after body timeout + +$s = Test::Nginx::HTTP2->new(port(8087)); +$sid = $s->new_stream({ path => '/proxy/t2.html', body_more => 1 }); +$s->h2_body('TEST', { split => [ 12 ], abort => 1 }); + +select undef, undef, undef, 1.1; +undef $s; + # proxied request with logging pristine request header field (e.g., referer) $s = Test::Nginx::HTTP2->new(); @@ -852,6 +825,7 @@ unlike($lengths, qr/16384 0 16384/, 'SETTINGS ack after queued DATA'); SKIP: { skip 'unsafe socket tests', 4 unless $ENV{TEST_NGINX_UNSAFE}; +$s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ path => '/tbig.html' }); $s->h2_window(2**30, $sid); @@ -1117,30 +1091,15 @@ is($frame->{value}, 'SEE-THIS', 'unknown frame type'); # graceful shutdown with stream waiting on HEADERS payload -my $grace = Test::Nginx::HTTP2->new(port(8084)); +my $grace = Test::Nginx::HTTP2->new(port(8087)); $grace->new_stream({ split => [ 9 ], abort => 1 }); -# graceful shutdown with stream waiting on WINDOW_UPDATE - -my $grace2 = Test::Nginx::HTTP2->new(port(8084)); -$sid = $grace2->new_stream({ path => '/t1.html' }); -$grace2->read(all => [{ sid => $sid, length => 2**16 - 1 }]); - # graceful shutdown waiting on incomplete request body DATA frames -my $grace3 = Test::Nginx::HTTP2->new(port(8085)); -$sid = $grace3->new_stream({ path => '/proxy2/t2.html', body_more => 1 }); +my $grace3 = Test::Nginx::HTTP2->new(port(8087)); +$sid = $grace3->new_stream({ path => '/proxy/t2.html', body_more => 1 }); $grace3->h2_body('TEST', { body_more => 1 }); -# partial request body data frame with connection close after body timeout - -my $grace4 = Test::Nginx::HTTP2->new(port(8087)); -$sid = $grace4->new_stream({ path => '/proxy/t2.html', body_more => 1 }); -$grace4->h2_body('TEST', { split => [ 12 ], abort => 1 }); - -select undef, undef, undef, 1.1; -undef $grace4; - # GOAWAY without awaiting active streams, further streams ignored $s = Test::Nginx::HTTP2->new(port(8080)); @@ -1170,7 +1129,7 @@ is($sum, 81, 'GOAWAY with active stream - active stream DATA after GOAWAY'); # GOAWAY - force closing a connection by server with idle or active streams -$s = Test::Nginx::HTTP2->new(); +$s = Test::Nginx::HTTP2->new(port(8086)); $sid = $s->new_stream(); $s->read(all => [{ sid => $sid, fin => 1 }]); diff --git a/tests/nginx-tests/nginx-tests/h2_absolute_redirect.t b/tests/nginx-tests/nginx-tests/h2_absolute_redirect.t new file mode 100644 index 0000000000..200821bb78 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/h2_absolute_redirect.t @@ -0,0 +1,219 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for absolute_redirect directive and Location escaping. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::HTTP2; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + absolute_redirect off; + + server { + listen 127.0.0.1:8080 http2; + server_name on; + + absolute_redirect on; + error_page 400 /return301; + + location / { } + + location /auto/ { + proxy_pass http://127.0.0.1:8080; + } + + location "/auto sp/" { + proxy_pass http://127.0.0.1:8080; + } + + location /return301 { + return 301 /redirect; + } + + location /return301/name { + return 301 /redirect; + server_name_in_redirect on; + } + + location /return301/port { + return 301 /redirect; + port_in_redirect off; + } + + location /i/ { + alias %%TESTDIR%%/; + } + } + + server { + listen 127.0.0.1:8080 http2; + server_name off; + + location / { } + + location /auto/ { + proxy_pass http://127.0.0.1:8080; + } + + location "/auto sp/" { + proxy_pass http://127.0.0.1:8080; + } + + location '/auto "#%<>?\^`{|}/' { + proxy_pass http://127.0.0.1:8080; + } + + location /return301 { + return 301 /redirect; + } + + location /i/ { + alias %%TESTDIR%%/; + } + } +} + +EOF + +mkdir($t->testdir() . '/dir'); +mkdir($t->testdir() . '/dir sp'); + +$t->run()->plan(23); + +############################################################################### + +my $p = port(8080); + +like(get('on', '/dir'), qr!Location: http://on:$p/dir/\x0d?$!m, 'directory'); +like(get('on', '/i/dir'), qr!Location: http://on:$p/i/dir/\x0d?$!m, + 'directory alias'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +like(get('on', '/dir%20sp'), qr!Location: http://on:$p/dir%20sp/\x0d?$!m, + 'directory escaped'); +like(get('on', '/dir%20sp?a=b'), + qr!Location: http://on:$p/dir%20sp/\?a=b\x0d?$!m, + 'directory escaped args'); + +} + +like(get('on', '/auto'), qr!Location: http://on:$p/auto/\x0d?$!m, 'auto'); +like(get('on', '/auto?a=b'), qr!Location: http://on:$p/auto/\?a=b\x0d?$!m, + 'auto args'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +like(get('on', '/auto%20sp'), qr!Location: http://on:$p/auto%20sp/\x0d?$!m, + 'auto escaped'); +like(get('on', '/auto%20sp?a=b'), + qr!Location: http://on:$p/auto%20sp/\?a=b\x0d?$!m, + 'auto escaped args'); + +} + +like(get('on', '/return301'), qr!Location: http://on:$p/redirect\x0d?$!m, + 'return'); + +like(get('host', '/return301/name'), qr!Location: http://on:$p/redirect\x0d?!m, + 'server_name_in_redirect on'); +like(get('host', '/return301'), qr!Location: http://host:$p/redirect\x0d?$!m, + 'server_name_in_redirect off - using host'); +my $ph = IO::Socket::INET->new("127.0.0.1:$p")->peerhost(); +like(get('.', '/return301'), qr!Location: http://$ph:$p/redirect\x0d?$!m, + 'invalid host - using local sockaddr'); +like(get('host', '/return301/port'), qr!Location: http://host/redirect\x0d?$!m, + 'port_in_redirect off'); + +like(get('off', '/dir'), qr!Location: /dir/\x0d?$!m, 'off directory'); +like(get('off', '/i/dir'), qr!Location: /i/dir/\x0d?$!m, 'off directory alias'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +like(get('off', '/dir%20sp'), qr!Location: /dir%20sp/\x0d?$!m, + 'off directory escaped'); +like(get('off', '/dir%20sp?a=b'), qr!Location: /dir%20sp/\?a=b\x0d?$!m, + 'off directory escaped args'); + +} + +like(get('off', '/auto'), qr!Location: /auto/\x0d?$!m, 'off auto'); +like(get('off', '/auto?a=b'), qr!Location: /auto/\?a=b\x0d?$!m, + 'off auto args'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +like(get('off', '/auto%20sp'), qr!Location: /auto%20sp/\x0d?$!m, + 'auto escaped'); +like(get('off', '/auto%20sp?a=b'), qr!Location: /auto%20sp/\?a=b\x0d?$!m, + 'auto escaped args'); + +} + +like(get('off', '/return301'), qr!Location: /redirect\x0d?$!m, 'off return'); + +# per RFC 3986, these characters are not allowed unescaped: +# %00-%1F, %7F-%FF, " ", """, "<", ">", "\", "^", "`", "{", "|", "}" +# additionally, all characters in ESCAPE_URI: "?", "%", "#" + +SKIP: { +skip 'win32', 1 if $^O eq 'MSWin32'; + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.1'); + +like(get('off', '/auto%20%22%23%25%3C%3E%3F%5C%5E%60%7B%7C%7D'), + qr!Location: /auto%20%22%23%25%3C%3E%3F%5C%5E%60%7B%7C%7D/\x0d?$!m, + 'auto escaped strict'); + +} + +} + +############################################################################### + +sub get { + my ($host, $uri) = @_; + + my $s = Test::Nginx::HTTP2->new(); + my $sid = $s->new_stream({ host => $host, path => $uri }); + my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + + my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; + return 'Location: ' . $frame->{headers}->{'location'}; +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/h2_headers.t b/tests/nginx-tests/nginx-tests/h2_headers.t index d407c77d08..db0db218b2 100644 --- a/tests/nginx-tests/nginx-tests/h2_headers.t +++ b/tests/nginx-tests/nginx-tests/h2_headers.t @@ -3,8 +3,7 @@ # (C) Sergey Kandaurov # (C) Nginx, Inc. -# Tests for HTTP/2 protocol with headers. -# various HEADERS compression/encoding, see hpack() for mode details. +# Tests for HTTP/2 headers. ############################################################################### @@ -13,8 +12,6 @@ use strict; use Test::More; -use Config; - BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; @@ -26,7 +23,7 @@ use Test::Nginx::HTTP2; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite/)->plan(93) +my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite/)->plan(107) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -45,8 +42,7 @@ http { listen 127.0.0.1:8082 http2 sndbuf=128; server_name localhost; - http2_max_field_size 128k; - http2_max_header_size 128k; + large_client_header_buffers 2 64k; location / { add_header X-Sent-Foo $http_x_foo; @@ -96,14 +92,30 @@ http { listen 127.0.0.1:8084 http2; server_name localhost; - http2_max_field_size 22; + large_client_header_buffers 4 512; } server { listen 127.0.0.1:8085 http2; server_name localhost; - http2_max_header_size 64; + large_client_header_buffers 1 512; + } + + server { + listen 127.0.0.1:8086 http2; + server_name localhost; + + underscores_in_headers on; + add_header X-Sent-Foo $http_x_foo always; + } + + server { + listen 127.0.0.1:8087 http2; + server_name localhost; + + ignore_invalid_headers off; + add_header X-Sent-Foo $http_x_foo always; } } @@ -457,8 +469,20 @@ $sid = $s->new_stream({ headers => [ { name => 'referer', value => 'foo', mode => 0 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); -($frame) = grep { $_->{type} eq "HEADERS" } @$frames; -is($frame, undef, 'invalid index'); +($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; +is($frame->{code}, 0x9, 'invalid index'); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ headers => [ + { name => ':method', value => 'GET', mode => 0 }, + { name => ':scheme', value => 'http', mode => 0 }, + { name => ':path', value => '/', mode => 0 }, + { name => ':authority', value => 'localhost', mode => 1 }, + { name => 'unknown', value => 'foo', mode => 3 }]}); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + +($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; +is($frame->{code}, 0x9, 'invalid index in literal header field'); # 5.4.1. Connection Error Handling # An endpoint that encounters a connection error SHOULD first send a @@ -689,7 +713,7 @@ $sid = $s->new_stream({ headers => [ { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/t2.html', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, - { name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]}); + { name => 'x' x 511, value => 'value', mode => 2 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq 'DATA' } @$frames; @@ -700,7 +724,7 @@ $sid = $s->new_stream({ headers => [ { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/t2.html', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, - { name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]}); + { name => 'x' x 511, value => 'value', mode => 2 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq 'DATA' } @$frames; @@ -712,7 +736,7 @@ $sid = $s->new_stream({ headers => [ { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/t2.html', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, - { name => 'longname10' x 2 . 'xx', value => 'value', mode => 2 }]}); + { name => 'x' x 512, value => 'value', mode => 2 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq 'DATA' } @$frames; @@ -724,7 +748,7 @@ $sid = $s->new_stream({ headers => [ { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/t2.html', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, - { name => 'longname10' x 2 . 'xxx', value => 'value', mode => 2 }]}); + { name => 'x' x 513, value => 'value', mode => 2 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq 'DATA' } @$frames; @@ -738,7 +762,7 @@ $sid = $s->new_stream({ headers => [ { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/t2.html', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, - { name => 'name', value => 'valu5' x 4 . 'x', mode => 2 }]}); + { name => 'name', value => 'x' x 511, mode => 2 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq 'DATA' } @$frames; @@ -750,7 +774,7 @@ $sid = $s->new_stream({ headers => [ { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/t2.html', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, - { name => 'name', value => 'valu5' x 4 . 'xx', mode => 2 }]}); + { name => 'name', value => 'x' x 511, mode => 2 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq 'DATA' } @$frames; @@ -762,7 +786,7 @@ $sid = $s->new_stream({ headers => [ { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/t2.html', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, - { name => 'name', value => 'valu5' x 4 . 'xxx', mode => 2 }]}); + { name => 'name', value => 'x' x 513, mode => 2 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq 'DATA' } @$frames; @@ -776,7 +800,7 @@ $sid = $s->new_stream({ headers => [ { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/t2.html', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, - { name => 'longname9', value => 'x', mode => 2 }]}); + { name => 'longname', value => 'x' x 450, mode => 2 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq 'DATA' } @$frames; @@ -787,7 +811,7 @@ $sid = $s->new_stream({ headers => [ { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/t2.html', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, - { name => 'longname9', value => 'x', mode => 2 }]}); + { name => 'longname', value => 'x' x 450, mode => 2 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq 'DATA' } @$frames; @@ -799,7 +823,7 @@ $sid = $s->new_stream({ headers => [ { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/t2.html', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, - { name => 'longname9', value => 'xx', mode => 2 }]}); + { name => 'longname', value => 'x' x 451, mode => 2 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq 'DATA' } @$frames; @@ -811,7 +835,7 @@ $sid = $s->new_stream({ headers => [ { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/t2.html', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, - { name => 'longname9', value => 'xxx', mode => 2 }]}); + { name => 'longname', value => 'x' x 452, mode => 2 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq 'DATA' } @$frames; @@ -826,7 +850,7 @@ $sid = $s->new_stream({ headers => [ { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/t2.html', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, - { name => 'longname9', value => 'x', mode => 2 }]}); + { name => 'longname', value => 'x' x 400, mode => 2 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq 'DATA' } @$frames; @@ -837,7 +861,7 @@ $sid = $s->new_stream({ headers => [ { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/t2.html', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, - { name => 'longname9', value => 'x', mode => 0 }]}); + { name => 'longname', value => 'x' x 400, mode => 0 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq 'DATA' } @$frames; @@ -848,8 +872,8 @@ $sid = $s->new_stream({ headers => [ { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/t2.html', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, - { name => 'longname9', value => 'x', mode => 0 }, - { name => 'longname9', value => 'x', mode => 0 }]}); + { name => 'longname', value => 'x' x 400, mode => 0 }, + { name => 'longname', value => 'x' x 400, mode => 0 }]}); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq 'GOAWAY' } @$frames; @@ -953,6 +977,83 @@ $frames = $s->read(all => [{ type => 'HEADERS' }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; is($frame->{headers}->{'x-referer'}, 'see-this', 'after invalid header name'); +# other invalid header name characters as seen with ':' result in RST_STREAM + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ headers => [ + { name => ':method', value => 'GET', mode => 0 }, + { name => ':scheme', value => 'http', mode => 0 }, + { name => ':path', value => '/', mode => 0 }, + { name => ':authority', value => 'localhost', mode => 1 }, + { name => 'x:foo', value => "x-bar", mode => 2 }, + { name => 'referer', value => "see-this", mode => 1 }]}); +$frames = $s->read(all => [{ type => 'RST_STREAM' }]); + +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; +is($frame->{sid}, $sid, 'colon in header name - RST_STREAM sid'); +is($frame->{code}, 1, 'colon in header name - RST_STREAM code'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.1'); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ headers => [ + { name => ':method', value => 'GET', mode => 0 }, + { name => ':scheme', value => 'http', mode => 0 }, + { name => ':path', value => '/', mode => 0 }, + { name => ':authority', value => 'localhost', mode => 1 }, + { name => 'x foo', value => "bar", mode => 2 }]}); +$frames = $s->read(all => [{ type => 'RST_STREAM' }]); + +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; +ok($frame, 'space in header name - RST_STREAM sid'); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ headers => [ + { name => ':method', value => 'GET', mode => 0 }, + { name => ':scheme', value => 'http', mode => 0 }, + { name => ':path', value => '/', mode => 0 }, + { name => ':authority', value => 'localhost', mode => 1 }, + { name => "foo\x02", value => "bar", mode => 2 }]}); +$frames = $s->read(all => [{ type => 'RST_STREAM' }]); + +($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; +ok($frame, 'control in header name - RST_STREAM sid'); + +} + +# header name with underscore - underscores_in_headers on + +$s = Test::Nginx::HTTP2->new(port(8086)); +$sid = $s->new_stream({ headers => [ + { name => ':method', value => 'GET', mode => 0 }, + { name => ':scheme', value => 'http', mode => 0 }, + { name => ':path', value => '/', mode => 0 }, + { name => ':authority', value => 'localhost', mode => 1 }, + { name => 'x_foo', value => "x-bar", mode => 2 }, + { name => 'referer', value => "see-this", mode => 1 }]}); +$frames = $s->read(all => [{ type => 'HEADERS' }]); + +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{'x-sent-foo'}, 'x-bar', + 'underscore in header name - underscores_in_headers'); + +# header name with underscore - ignore_invalid_headers off + +$s = Test::Nginx::HTTP2->new(port(8087)); +$sid = $s->new_stream({ headers => [ + { name => ':method', value => 'GET', mode => 0 }, + { name => ':scheme', value => 'http', mode => 0 }, + { name => ':path', value => '/', mode => 0 }, + { name => ':authority', value => 'localhost', mode => 1 }, + { name => 'x_foo', value => "x-bar", mode => 2 }, + { name => 'referer', value => "see-this", mode => 1 }]}); +$frames = $s->read(all => [{ type => 'HEADERS' }]); + +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{'x-sent-foo'}, 'x-bar', + 'underscore in header name - ignore_invalid_headers'); + # missing mandatory request header ':scheme' $s = Test::Nginx::HTTP2->new(); @@ -986,6 +1087,89 @@ $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; is($frame->{headers}->{':status'}, 400, 'invalid path'); +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.1'); + +$sid = $s->new_stream({ path => "/t1.html\x02" }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 400, 'invalid path control'); + +} + +# ngx_http_v2_parse_int() error handling + +# NGX_ERROR + +$s = Test::Nginx::HTTP2->new(); +{ + local $SIG{PIPE} = 'IGNORE'; + syswrite($s->{socket}, pack("x2C3NC", 1, 0x1, 5, 1, 0xff)); +} +$frames = $s->read(all => [{ type => "GOAWAY" }]); + +($frame) = grep { $_->{type} eq 'GOAWAY' } @$frames; +is($frame->{code}, 0x6, 'invalid index length'); + +$s = Test::Nginx::HTTP2->new(); +{ + local $SIG{PIPE} = 'IGNORE'; + syswrite($s->{socket}, pack("x2C3NC2", 2, 0x1, 5, 1, 0x42, 0xff)); +} +$frames = $s->read(all => [{ type => "GOAWAY" }]); + +($frame) = grep { $_->{type} eq 'GOAWAY' } @$frames; +is($frame->{code}, 0x6, 'invalid literal length'); + +# NGX_DECLINED + +$s = Test::Nginx::HTTP2->new(); +{ + local $SIG{PIPE} = 'IGNORE'; + syswrite($s->{socket}, pack("x2C3NN", 5, 0x1, 5, 1, 0xffffffff)); +} +$frames = $s->read(all => [{ type => "GOAWAY" }]); + +($frame) = grep { $_->{type} eq 'GOAWAY' } @$frames; +is($frame->{code}, 0x9, 'too long index'); + +$s = Test::Nginx::HTTP2->new(); +{ + local $SIG{PIPE} = 'IGNORE'; + syswrite($s->{socket}, pack("x2C3NCN", 6, 0x1, 5, 1, 0x42, 0xffffffff)); +} +$frames = $s->read(all => [{ type => "GOAWAY" }]); + +($frame) = grep { $_->{type} eq 'GOAWAY' } @$frames; +is($frame->{code}, 0x9, 'too long literal'); + +# NGX_AGAIN + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ split => [35], split_delay => 1.1, headers => [ + { name => ':method', value => 'GET', mode => 3, huff => 0 }, + { name => ':scheme', value => 'http', mode => 3, huff => 0 }, + { name => ':path', value => '/', mode => 3, huff => 0 }, + { name => ':authority', value => 'localhost', mode => 3, huff => 0 }, + { name => 'referer', value => 'foo', mode => 3, huff => 0 }]}); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{'x-referer'}, 'foo', 'header split index'); + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ split => [37], split_delay => 1.1, headers => [ + { name => ':method', value => 'GET', mode => 3, huff => 0 }, + { name => ':scheme', value => 'http', mode => 3, huff => 0 }, + { name => ':path', value => '/', mode => 3, huff => 0 }, + { name => ':authority', value => 'localhost', mode => 3, huff => 0 }, + { name => 'referer', value => '1234' x 32, mode => 3, huff => 0 }]}); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{'x-referer'}, '1234' x 32, 'header split field length'); + ############################################################################### sub http_daemon { diff --git a/tests/nginx-tests/nginx-tests/h2_keepalive.t b/tests/nginx-tests/nginx-tests/h2_keepalive.t new file mode 100644 index 0000000000..8dae1adc42 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/h2_keepalive.t @@ -0,0 +1,194 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for HTTP/2 protocol, keepalive directives. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use Socket qw(SOL_SOCKET SO_RCVBUF); + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::HTTP2; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_v2/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080 http2 sndbuf=1m; + server_name localhost; + + keepalive_requests 2; + + location / { } + } + + server { + listen 127.0.0.1:8081 http2; + server_name localhost; + + keepalive_timeout 0; + + location / { } + } + + server { + listen 127.0.0.1:8082 http2; + server_name localhost; + + keepalive_time 1s; + + add_header X-Conn $connection_requests:$connection_time; + + location / { } + } +} + +EOF + +$t->write_file('index.html', 'SEE-THAT' x 50000); +$t->write_file('t.html', 'SEE-THAT'); + +$t->run()->plan(19); + +############################################################################### + +my $s = Test::Nginx::HTTP2->new(); + +# to test lingering close, let full response settle down in send buffer space +# so that client additional data past server-side close would trigger TCP RST + +$s->{socket}->setsockopt(SOL_SOCKET, SO_RCVBUF, 64*1024) or die $!; +$s->h2_settings(0, 0x4 => 2**20); +$s->h2_window(2**21); + +my $frames = $s->read(all => [{ sid => $s->new_stream(), fin => 1 }]); + +my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 200, 'max requests'); + +$frames = $s->read(all => [{ type => 'GOAWAY' }], wait => 0.5) + unless grep { $_->{type} eq "GOAWAY" } @$frames; + +($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; +is($frame, undef, 'max requests - GOAWAY'); + +# max requests limited + +my $sid = $s->new_stream(); + +# wait server to finish and close socket if lingering close were disabled + +select undef, undef, undef, 0.1; +$s->h2_ping("SEE-THIS"); + +$frames = $s->read(all => [{ sid => $sid, fin => 1 }, { type => 'GOAWAY' }]); + +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 200, 'max requests limited'); + +my @data = grep { $_->{type} eq "DATA" } @$frames; +my $sum = eval join '+', map { $_->{length} } @data; +is($sum, 400000, 'max requests limited - all data received'); + +($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; +ok($frame, 'max requests limited - GOAWAY'); +is($frame->{last_sid}, $sid, 'max requests limited - GOAWAY last stream'); + +# keepalive_timeout 0 + +$s = Test::Nginx::HTTP2->new(port(8081)); +$sid = $s->new_stream({ path => '/t.html' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }, { type => 'GOAWAY' }]); + +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 200, 'keepalive_timeout 0'); + +($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; +ok($frame, 'keepalive_timeout 0 - GOAWAY'); + +# keepalive_time + +$s = Test::Nginx::HTTP2->new(port(8082)); +$sid = $s->new_stream({ path => '/t.html' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 200, 'keepalive time request'); +like($frame->{headers}->{'x-conn'}, qr/^1:0/, 'keepalive time variables'); + +$frames = $s->read(all => [{ type => 'GOAWAY' }], wait => 0.5); + +($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; +is($frame, undef, 'keepalive time - no GOAWAY yet'); + +select undef, undef, undef, 1.1; + +$sid = $s->new_stream({ path => '/t.html' }); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }, { type => 'GOAWAY' }]); + +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 200, 'keepalive time request 2'); +like($frame->{headers}->{'x-conn'}, qr/^2:[^0]/, 'keepalive time variables 2'); + +($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; +ok($frame, 'keepalive time limit - GOAWAY'); +is($frame->{last_sid}, $sid, 'keepalive time limit - GOAWAY last stream'); + +# graceful shutdown in idle state + +$s = Test::Nginx::HTTP2->new(); +$s->{socket}->setsockopt(SOL_SOCKET, SO_RCVBUF, 64*1024) or die $!; +$s->h2_settings(0, 0x4 => 2**20); +$s->h2_window(2**21); + +$sid = $s->new_stream(); + +# wait server to finish and close socket if lingering close were disabled + +select undef, undef, undef, 0.1; + +$t->reload(); + +select undef, undef, undef, 0.3; + +$s->h2_ping("SEE-THIS"); + +$frames = $s->read(all => [{ sid => $sid, fin => 1 }, { type => 'GOAWAY' }]); + +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 200, 'graceful shutdown in idle'); + +@data = grep { $_->{type} eq "DATA" } @$frames; +$sum = eval join '+', map { $_->{length} } @data; +is($sum, 400000, 'graceful shutdown in idle - all data received'); + +($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; +ok($frame, 'graceful shutdown in idle - GOAWAY'); +is($frame->{last_sid}, $sid, 'graceful shutdown in idle - GOAWAY last stream'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/h2_limit_req.t b/tests/nginx-tests/nginx-tests/h2_limit_req.t index f5289e9718..963efec6eb 100644 --- a/tests/nginx-tests/nginx-tests/h2_limit_req.t +++ b/tests/nginx-tests/nginx-tests/h2_limit_req.t @@ -104,11 +104,6 @@ is($frame->{headers}->{':status'}, 200, 'request body - limit req - empty'); # predict send windows $sid = $s->new_stream(); -my ($maxwin) = sort {$a <=> $b} $s->{streams}{$sid}, $s->{conn_window}; - -SKIP: { -skip 'not enough window', 1 if $maxwin < 5; - $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ path => '/proxy_limit_req/', body => 'TEST2' }); select undef, undef, undef, 1.1; @@ -118,16 +113,10 @@ $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST2', 'request body - limit req 2'); -} - # partial request body data frame received (to be discarded) within request # delayed in limit_req, the rest of data frame is received after response $s = Test::Nginx::HTTP2->new(); - -SKIP: { -skip 'not enough window', 1 if $maxwin < 4; - $sid = $s->new_stream({ path => '/limit_req', body => 'TEST', split => [61], split_delay => 1.1 }); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); @@ -135,8 +124,6 @@ $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; is($frame->{headers}->{':status'}, '200', 'discard body - limit req - limited'); -} - $sid = $s->new_stream({ path => '/' }); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); @@ -146,9 +133,6 @@ is($frame->{headers}->{':status'}, '200', 'discard body - limit req - next'); # ditto, but instead of receiving the rest of data frame, connection is closed # 'http request already closed while closing request' alert can be produced -SKIP: { -skip 'not enough window', 1 if $maxwin < 4; - $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ path => '/limit_req', body => 'TEST', split => [61], abort => 1 }); @@ -158,8 +142,6 @@ close $s->{socket}; pass('discard body - limit req - eof'); -} - ############################################################################### sub read_body_file { diff --git a/tests/nginx-tests/nginx-tests/h2_priority.t b/tests/nginx-tests/nginx-tests/h2_priority.t index d17c7f5ae4..73b4f37fe1 100644 --- a/tests/nginx-tests/nginx-tests/h2_priority.t +++ b/tests/nginx-tests/nginx-tests/h2_priority.t @@ -208,9 +208,6 @@ is($sids, "$sid $sid2", 'dependency - PRIORITY 2'); # stream error of type PROTOCOL_ERROR. # Instead, we respond with a connection error of type PROTOCOL_ERROR. -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.17.4'); - $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream(); $s->read(all => [{ sid => $sid, fin => 1 }]); @@ -222,8 +219,6 @@ my ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; is($frame->{last_sid}, $sid, 'dependency - PRIORITY self - GOAWAY'); is($frame->{code}, 1, 'dependency - PRIORITY self - PROTOCOL_ERROR'); -} - # HEADERS PRIORITY flag, reprioritize prior PRIORITY frame records $s = Test::Nginx::HTTP2->new(); @@ -278,19 +273,14 @@ is($sids, "$sid $sid2", 'dependency - HEADERS PRIORITY 2'); # HEADERS - self dependency -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.17.4'); - $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ dep => 1 }); $frames = $s->read(all => [{ type => 'GOAWAY' }]); -my ($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; +($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; is($frame->{last_sid}, 0, 'dependency - HEADERS self - GOAWAY'); is($frame->{code}, 1, 'dependency - HEADERS self - PROTOCOL_ERROR'); -} - # PRIORITY frame, weighted dependencies $s = Test::Nginx::HTTP2->new(); @@ -402,7 +392,7 @@ $frames = $s->read(all => [ { sid => $sid3, fin => 1 }, ]); -my ($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid } @$frames; +($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid } @$frames; is($frame->{length}, 81, 'removed dependency - first stream'); ($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid3 } @$frames; diff --git a/tests/nginx-tests/nginx-tests/h2_proxy_cache.t b/tests/nginx-tests/nginx-tests/h2_proxy_cache.t index 43f3169d0e..106983832f 100644 --- a/tests/nginx-tests/nginx-tests/h2_proxy_cache.t +++ b/tests/nginx-tests/nginx-tests/h2_proxy_cache.t @@ -23,7 +23,7 @@ use Test::Nginx::HTTP2; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http http_v2 proxy cache/)->plan(12) +my $t = Test::Nginx->new()->has(qw/http http_v2 proxy cache/)->plan(9) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -38,10 +38,6 @@ http { proxy_cache_path %%TESTDIR%%/cache keys_zone=NAME:1m; - # quit unfixed nginx timely on different linuces - http2_idle_timeout 2s; - http2_recv_timeout 2s; - server { listen 127.0.0.1:8080 http2; listen 127.0.0.1:8081; @@ -130,22 +126,6 @@ $frames = $s->read(all => [{ sid => $sid, fin => 1 }], wait => 0.2); push @$frames, $_ for @{$s->read(all => [{ sid => $sid }], wait => 0.2)}; ok(!grep ({ $_->{type} eq "DATA" } @$frames), 'proxy cache HEAD - no body'); -# proxy cache - expect no stray empty DATA frame - -TODO: { -local $TODO = 'not yet'; - -$s = Test::Nginx::HTTP2->new(); -$sid = $s->new_stream({ path => '/cache/t.html?2' }); - -$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); -my @data = grep ({ $_->{type} eq "DATA" } @$frames); -is(@data, 1, 'proxy cache write - data frames'); -is(join(' ', map { $_->{data} } @data), 'SEE-THIS', 'proxy cache write - data'); -is(join(' ', map { $_->{flags} } @data), '1', 'proxy cache write - flags'); - -} - # HEAD on empty cache with proxy_buffering off $s = Test::Nginx::HTTP2->new(); diff --git a/tests/nginx-tests/nginx-tests/h2_proxy_max_temp_file_size.t b/tests/nginx-tests/nginx-tests/h2_proxy_max_temp_file_size.t new file mode 100644 index 0000000000..e784274c01 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/h2_proxy_max_temp_file_size.t @@ -0,0 +1,101 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http proxy module, proxy_max_temp_file_size directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::HTTP2; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_v2 proxy/)->plan(4); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080 http2; + server_name localhost; + + proxy_buffer_size 4k; + proxy_buffers 8 4k; + + location / { + proxy_max_temp_file_size 4k; + proxy_pass http://127.0.0.1:8081/; + } + + location /off/ { + proxy_max_temp_file_size 0; + proxy_pass http://127.0.0.1:8081/; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location / { } + } +} + +EOF + +$t->write_file('1', 'X' x (1024 * 1024)); +$t->run(); + +############################################################################### + +# test that the response is wholly proxied when all event pipe buffers are full + +my $s = Test::Nginx::HTTP2->new(); +my $sid = $s->new_stream({ path => '/1' }); + +select undef, undef, undef, 0.4; +$s->h2_window(1024 * 1024, $sid); +$s->h2_window(1024 * 1024); + +my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +my $body = join '', map { $_->{data} } grep { $_->{type} eq "DATA" } @$frames; +like($body, qr/^X+$/m, 'no pipe bufs - body'); +is(length($body), 1024 * 1024, 'no pipe bufs - body length'); + +# also with disabled proxy temp file + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => '/off/1' }); + +select undef, undef, undef, 0.4; +$s->h2_window(1024 * 1024, $sid); +$s->h2_window(1024 * 1024); + +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +$body = join '', map { $_->{data} } grep { $_->{type} eq "DATA" } @$frames; +like($body, qr/^X+$/m, 'no temp file - body'); +is(length($body), 1024 * 1024, 'no temp file - body length'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/h2_proxy_request_buffering_redirect.t b/tests/nginx-tests/nginx-tests/h2_proxy_request_buffering_redirect.t new file mode 100644 index 0000000000..780588002f --- /dev/null +++ b/tests/nginx-tests/nginx-tests/h2_proxy_request_buffering_redirect.t @@ -0,0 +1,86 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for HTTP/2 protocol with unbuffered request body. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::HTTP2; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite/)->plan(1); + +$t->write_file_expand('nginx.conf', <<'EOF')->run(); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080 http2; + listen 127.0.0.1:8081; + server_name localhost; + + proxy_http_version 1.1; + + location / { + proxy_request_buffering off; + proxy_pass http://127.0.0.1:8081/bad; + proxy_intercept_errors on; + error_page 502 = /pass; + } + + location /bad { + return 502; + } + + location /pass { + proxy_pass http://127.0.0.1:8081/good; + } + + location /good { + limit_rate 100; + return 200; + } + } +} + +EOF + +############################################################################### + +# unbuffered request body + +my $s = Test::Nginx::HTTP2->new(); +my $sid = $s->new_stream({ body_more => 1 }); + +$s->h2_body('SEE-', { body_more => 1 }); +sleep 1; +$s->h2_body('THIS'); + +my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 200, 'discard body rest on redirect'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/h2_request_body.t b/tests/nginx-tests/nginx-tests/h2_request_body.t index f453f3631a..c9f4cdb520 100644 --- a/tests/nginx-tests/nginx-tests/h2_request_body.t +++ b/tests/nginx-tests/nginx-tests/h2_request_body.t @@ -23,7 +23,7 @@ use Test::Nginx::HTTP2; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http http_v2 proxy/)->plan(44); +my $t = Test::Nginx->new()->has(qw/http http_v2 proxy/)->plan(49); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -42,6 +42,8 @@ http { listen 127.0.0.1:8081; server_name localhost; + error_page 400 /proxy2/t.html; + location / { add_header X-Length $http_content_length; } @@ -82,8 +84,7 @@ $t->run(); # request body (uses proxied response) my $s = Test::Nginx::HTTP2->new(); -my $sid = $s->new_stream({ path => '/proxy2/t.html', body_more => 1 }); -$s->h2_body('TEST'); +my $sid = $s->new_stream({ path => '/proxy2/t.html', body => 'TEST' }); my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; @@ -93,8 +94,8 @@ is($frame->{headers}->{'x-length'}, 4, 'request body - content length'); # request body with padding (uses proxied response) $s = Test::Nginx::HTTP2->new(); -$sid = $s->new_stream({ path => '/proxy2/t.html', body_more => 1 }); -$s->h2_body('TEST', { body_padding => 42 }); +$sid = $s->new_stream( + { path => '/proxy2/t.html', body => 'TEST', body_padding => 42 }); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; @@ -112,8 +113,8 @@ is($frame->{headers}->{':status'}, '200', 'request body with padding - next'); # request body sent in multiple DATA frames in a single packet $s = Test::Nginx::HTTP2->new(); -$sid = $s->new_stream({ path => '/proxy2/t.html', body_more => 1 }); -$s->h2_body('TEST', { body_split => [2] }); +$sid = $s->new_stream( + { path => '/proxy2/t.html', body => 'TEST', body_split => [2] }); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; @@ -137,12 +138,38 @@ is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTMOREDATA', is($frame->{headers}->{'x-length'}, 12, 'request body in multiple frames separately - content length'); +# if run with body buffering in filters, it's expected to update window +# after request body populates initial stream window size set for preread + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => '/proxy2/t.html', body_more => 1 }); +$s->h2_body('01234567' x 2048, { body_more => 1 }); +select undef, undef, undef, 0.1; +$s->h2_body('01234567' x 2048, { body_more => 1 }); +select undef, undef, undef, 0.1; +$s->h2_body('01234567' x 2048, { body_more => 1 }); +select undef, undef, undef, 0.1; +$s->h2_body('01234567' x 2048, { body_more => 1 }); + +$frames = $s->read(all => [{ type => 'WINDOW_UPDATE' }]); +($frame) = grep { $_->{type} eq 'WINDOW_UPDATE' } @$frames; +is($frame->{sid}, $sid, 'big request body - WINDOW_UPDATE sid'); +cmp_ok($frame->{wdelta}, '>=', 65536, 'big request body - WINDOW_UPDATE delta'); + +$s->h2_body('01234567' x 2048); + +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is(read_body_file($frame->{headers}->{'x-body-file'}), '01234567' x 10240, + 'big request body - content'); +is($frame->{headers}->{'x-length'}, 81920, + 'big request body - content length'); + # request body with an empty DATA frame # "zero size buf in output" alerts seen $s = Test::Nginx::HTTP2->new(); -$sid = $s->new_stream({ path => '/proxy2/', body_more => 1 }); -$s->h2_body(''); +$sid = $s->new_stream({ path => '/proxy2/', body => '' }); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; @@ -166,9 +193,6 @@ is($frame->{headers}->{'x-length'}, undef, # request body discarded # RST_STREAM with zero code received -TODO: { -local $TODO = 'not yet'; - $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ body_more => 1 }); $frames = $s->read(all => [{ type => 'RST_STREAM' }], wait => 0.5); @@ -176,30 +200,26 @@ $frames = $s->read(all => [{ type => 'RST_STREAM' }], wait => 0.5); ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames; is($frame->{code}, 0, 'request body discarded - zero RST_STREAM'); -} - # malformed request body length not equal to content-length $s = Test::Nginx::HTTP2->new(); -$sid = $s->new_stream({ body_more => 1, headers => [ +$sid = $s->new_stream({ body => 'TEST', headers => [ { name => ':method', value => 'GET', mode => 0 }, { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/client_max_body_size', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, { name => 'content-length', value => '5', mode => 1 }]}); -$s->h2_body('TEST'); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; is($frame->{headers}->{':status'}, 400, 'request body less than content-length'); -$sid = $s->new_stream({ body_more => 1, headers => [ +$sid = $s->new_stream({ body => 'TEST', headers => [ { name => ':method', value => 'GET', mode => 0 }, { name => ':scheme', value => 'http', mode => 0 }, { name => ':path', value => '/client_max_body_size', mode => 1 }, { name => ':authority', value => 'localhost', mode => 1 }, { name => 'content-length', value => '3', mode => 1 }]}); -$s->h2_body('TEST'); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; @@ -209,8 +229,7 @@ is($frame->{headers}->{':status'}, 400, 'request body more than content-length') $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ path => '/client_max_body_size/t.html', - body_more => 1 }); -$s->h2_body('TESTTEST12'); + body => 'TESTTEST12' }); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; @@ -222,8 +241,7 @@ is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12', $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ path => '/client_max_body_size/t.html', - body_more => 1 }); -$s->h2_body('TESTTEST123'); + body => 'TESTTEST123' }); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; @@ -233,8 +251,7 @@ is($frame->{headers}->{':status'}, 413, 'client_max_body_size - limited'); $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ path => '/client_max_body_size/t.html', - body_more => 1 }); -$s->h2_body('TESTTEST12', { body_split => [2] }); + body => 'TESTTEST12', body_split => [2] }); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; @@ -246,8 +263,7 @@ is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12', $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ path => '/client_max_body_size/t.html', - body_more => 1 }); -$s->h2_body('TESTTEST123', { body_split => [2] }); + body => 'TESTTEST123', body_split => [2] }); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; @@ -257,8 +273,7 @@ is($frame->{headers}->{':status'}, 413, 'client_max_body_size many - limited'); $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ path => '/client_max_body_size/t.html', - body_more => 1 }); -$s->h2_body('TESTTEST12', { body_padding => 42 }); + body => 'TESTTEST12', body_padding => 42 }); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; @@ -270,8 +285,7 @@ is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12', $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ path => '/client_max_body_size/t.html', - body_more => 1 }); -$s->h2_body('TESTTEST123', { body_padding => 42 }); + body => 'TESTTEST123', body_padding => 42 }); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; @@ -281,8 +295,7 @@ is($frame->{headers}->{':status'}, 413, 'client_max_body_size pad - limited'); $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ path => '/client_max_body_size/t.html', - body_more => 1 }); -$s->h2_body('TESTTEST12', { body_padding => 42, body_split => [2] }); + body => 'TESTTEST12', body_padding => 42, body_split => [2] }); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; @@ -295,8 +308,7 @@ is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12', $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ path => '/client_max_body_size/t.html', - body_more => 1 }); -$s->h2_body('TESTTEST123', { body_padding => 42, body_split => [2] }); + body => 'TESTTEST123', body_padding => 42, body_split => [2] }); $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; @@ -461,6 +473,16 @@ $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; isnt($frame->{headers}->{'x-body'}, 'xxxx', 'sync buffer'); +# request body after 400 errors redirected to a proxied location + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ body => "", headers => [ + { name => ':method', value => "" }]}); + +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +($frame) = grep { $_->{type} eq 'DATA' } @$frames; +is($frame->{data}, 'SEE-THIS', 'request body after 400 redirect'); + ############################################################################### sub read_body_file { diff --git a/tests/nginx-tests/nginx-tests/h2_request_body_extra.t b/tests/nginx-tests/nginx-tests/h2_request_body_extra.t new file mode 100644 index 0000000000..38d3db031d --- /dev/null +++ b/tests/nginx-tests/nginx-tests/h2_request_body_extra.t @@ -0,0 +1,331 @@ +#!/usr/bin/perl + +# (C) Maxim Dounin +# (C) Nginx, Inc. + +# Tests for HTTP/2 protocol with request body, additional tests. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::HTTP2; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080 http2; + listen 127.0.0.1:8081; + server_name localhost; + + client_header_buffer_size 1k; + client_body_buffer_size 2k; + + location / { + add_header X-Body $request_body; + add_header X-Body-File $request_body_file; + proxy_pass http://127.0.0.1:8082; + } + + location /file { + client_body_in_file_only on; + add_header X-Body "$request_body"; + add_header X-Body-File "$request_body_file"; + proxy_pass http://127.0.0.1:8082; + } + + location /single { + client_body_in_single_buffer on; + add_header X-Body "$request_body"; + add_header X-Body-File "$request_body_file"; + proxy_pass http://127.0.0.1:8082; + } + + location /large { + client_max_body_size 1k; + proxy_pass http://127.0.0.1:8082; + } + + location /unbuf/ { + add_header X-Unbuf-File "$request_body_file"; + proxy_pass http://127.0.0.1:8081/; + proxy_request_buffering off; + proxy_http_version 1.1; + } + } + + server { + listen 127.0.0.1:8082; + server_name localhost; + return 204; + } +} + +EOF + +plan(skip_all => 'not yet') unless $t->has_version('1.21.2'); +$t->plan(50); + +$t->run(); + +############################################################################### + +# below are basic body tests from body.t, slightly +# adapted to HTTP/2, repeated multiple times with variations: +# +# buffered vs. non-buffered, length vs. chunked, +# single frame vs. multiple frames +# +# some does not make sense in HTTP/2 (such as "body in two buffers"), but +# preserved for consistency and due to the fact that proxying via HTTP/1.1 +# is used in unbuffered tests + +unlike(http2_get('/'), qr/x-body:/ms, 'no body'); + +like(http2_get_body('/', '0123456789'), + qr/x-body: 0123456789$/ms, 'body'); +like(http2_get_body('/', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, 'body in two buffers'); +like(http2_get_body('/', '0123456789' x 512), + qr/x-body-file/ms, 'body in file'); +like(read_body_file(http2_get_body('/file', '0123456789' x 512)), + qr/^(0123456789){512}$/s, 'body in file only'); +like(http2_get_body('/single', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, 'body in single buffer'); +like(http2_get_body('/large', '0123456789' x 128), + qr/:status: 413/, 'body too large'); + +# without Content-Length header + +like(http2_get_body_nolen('/', '0123456789'), + qr/x-body: 0123456789$/ms, 'body nolen'); +like(http2_get_body_nolen('/', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, 'body nolen in two buffers'); +like(http2_get_body_nolen('/', '0123456789' x 512), + qr/x-body-file/ms, 'body nolen in file'); +like(read_body_file(http2_get_body_nolen('/file', '0123456789' x 512)), + qr/^(0123456789){512}$/s, 'body nolen in file only'); +like(http2_get_body_nolen('/single', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, 'body nolen in single buffer'); +like(http2_get_body_nolen('/large', '0123456789' x 128), + qr/:status: 413/, 'body nolen too large'); + +# with multiple frames + +like(http2_get_body_multi('/', '0123456789'), + qr/x-body: 0123456789$/ms, 'body multi'); +like(http2_get_body_multi('/', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, 'body multi in two buffers'); +like(http2_get_body_multi('/', '0123456789' x 512), + qr/x-body-file/ms, 'body multi in file'); +like(read_body_file(http2_get_body_multi('/file', '0123456789' x 512)), + qr/^(0123456789){512}$/s, 'body multi in file only'); +like(http2_get_body_multi('/single', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, 'body multi in single buffer'); +like(http2_get_body_multi('/large', '0123456789' x 128), + qr/:status: 413/, 'body multi too large'); + +# with multiple frames and without Content-Length header + +like(http2_get_body_multi_nolen('/', '0123456789'), + qr/x-body: 0123456789$/ms, 'body multi nolen'); +like(http2_get_body_multi_nolen('/', '0123456789' x 128), + qr/x-body: (0123456789){128}/ms, 'body multi nolen in two buffers'); +like(http2_get_body_multi_nolen('/', '0123456789' x 512), + qr/x-body-file/ms, 'body multi nolen in file'); +like(read_body_file(http2_get_body_multi_nolen('/file', '0123456789' x 512)), + qr/^(0123456789){512}$/s, 'body multi nolen in file only'); +like(http2_get_body_multi_nolen('/single', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, 'body multi nolen in single buffer'); +like(http2_get_body_multi_nolen('/large', '0123456789' x 128), + qr/:status: 413/, 'body multi nolen too large'); + +# unbuffered + +unlike(http2_get('/unbuf/'), qr/x-body:/ms, 'no body unbuf'); + +like(http2_get_body('/unbuf/', '0123456789'), + qr/x-body: 0123456789$/ms, 'body unbuf'); +like(http2_get_body('/unbuf/', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, 'body unbuf in two buffers'); +like(http2_get_body('/unbuf/', '0123456789' x 512), + qr/(?!.*x-unbuf-file.*)x-body-file/ms, 'body unbuf in file'); +like(read_body_file(http2_get_body('/unbuf/file', '0123456789' x 512)), + qr/^(0123456789){512}$/s, 'body unbuf in file only'); +like(http2_get_body('/unbuf/single', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, 'body unbuf in single buffer'); +like(http2_get_body('/unbuf/large', '0123456789' x 128), + qr/:status: 413/, 'body unbuf too large'); + +# unbuffered without Content-Length + +like(http2_get_body_nolen('/unbuf/', '0123456789'), + qr/x-body: 0123456789$/ms, 'body unbuf nolen'); +like(http2_get_body_nolen('/unbuf/', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, 'body unbuf nolen in two buffers'); +like(http2_get_body_nolen('/unbuf/', '0123456789' x 512), + qr/(?!.*x-unbuf-file.*)x-body-file/ms, 'body unbuf nolen in file'); +like(read_body_file(http2_get_body_nolen('/unbuf/file', '0123456789' x 512)), + qr/^(0123456789){512}$/s, 'body unbuf nolen in file only'); +like(http2_get_body_nolen('/unbuf/single', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, 'body unbuf nolen in single buffer'); +like(http2_get_body_nolen('/unbuf/large', '0123456789' x 128), + qr/:status: 413/, 'body unbuf nolen too large'); + +# unbuffered with multiple frames + +like(http2_get_body_multi('/unbuf/', '0123456789'), + qr/x-body: 0123456789$/ms, 'body unbuf multi'); +like(http2_get_body_multi('/unbuf/', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, 'body unbuf multi in two buffers'); +like(http2_get_body_multi('/unbuf/', '0123456789' x 512), + qr/(?!.*x-unbuf-file.*)x-body-file/ms, 'body unbuf multi in file'); +like(read_body_file(http2_get_body_multi('/unbuf/file', '0123456789' x 512)), + qr/^(0123456789){512}$/s, 'body unbuf multi in file only'); +like(http2_get_body_multi('/unbuf/single', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, 'body unbuf multi in single buffer'); +like(http2_get_body_multi('/unbuf/large', '0123456789' x 128), + qr/:status: 413/, 'body unbuf multi too large'); + +# unbuffered with multiple frames and without Content-Length + +like(http2_get_body_multi_nolen('/unbuf/', '0123456789'), + qr/x-body: 0123456789$/ms, 'body unbuf multi nolen'); +like(http2_get_body_multi_nolen('/unbuf/', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, + 'body unbuf multi nolen in two buffers'); +like(http2_get_body_multi_nolen('/unbuf/', '0123456789' x 512), + qr/(?!.*x-unbuf-file.*)x-body-file/ms, + 'body unbuf multi nolen in file'); +like(read_body_file(http2_get_body_multi_nolen('/unbuf/file', + '0123456789' x 512)), qr/^(0123456789){512}$/s, + 'body unbuf multi nolen in file only'); +like(http2_get_body_multi_nolen('/unbuf/single', '0123456789' x 128), + qr/x-body: (0123456789){128}$/ms, + 'body unbuf multi nolen in single buffer'); +like(http2_get_body_multi_nolen('/unbuf/large', '0123456789' x 128), + qr/:status: 413/, 'body unbuf multi nolen too large'); + +############################################################################### + +sub http2_get { + my ($uri) = @_; + + my $s = Test::Nginx::HTTP2->new(); + my $sid = $s->new_stream({ path => $uri }); + my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + + my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; + + return join("\n", map { "$_: " . $frame->{headers}->{$_}; } + keys %{$frame->{headers}}); +} + +sub http2_get_body { + my ($uri, $body) = @_; + + my $s = Test::Nginx::HTTP2->new(); + my $sid = $s->new_stream({ path => $uri, body => $body }); + my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + + my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; + + return join("\n", map { "$_: " . $frame->{headers}->{$_}; } + keys %{$frame->{headers}}); +} + +sub http2_get_body_nolen { + my ($uri, $body) = @_; + + my $s = Test::Nginx::HTTP2->new(); + my $sid = $s->new_stream({ path => $uri, body_more => 1 }); + $s->h2_body($body); + my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + + my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; + + return join("\n", map { "$_: " . $frame->{headers}->{$_}; } + keys %{$frame->{headers}}); +} + +sub http2_get_body_multi { + my ($uri, $body) = @_; + + my $s = Test::Nginx::HTTP2->new(); + my $sid = $s->new_stream({ + headers => [ + { name => ':method', value => 'GET' }, + { name => ':scheme', value => 'http' }, + { name => ':path', value => $uri }, + { name => ':authority', value => 'localhost' }, + { name => 'content-length', value => length $body }, + ], + body_more => 1 + }); + for my $b (split //, $body, 10) { + $s->h2_body($b, { body_more => 1 }); + } + select undef, undef, undef, 0.1; + $s->h2_body(''); + my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + + my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; + + return join("\n", map { "$_: " . $frame->{headers}->{$_}; } + keys %{$frame->{headers}}); +} + +sub http2_get_body_multi_nolen { + my ($uri, $body) = @_; + + my $s = Test::Nginx::HTTP2->new(); + my $sid = $s->new_stream({ path => $uri, body_more => 1 }); + for my $b (split //, $body, 10) { + $s->h2_body($b, { body_more => 1 }); + } + select undef, undef, undef, 0.1; + $s->h2_body(''); + my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + + my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; + + return join("\n", map { "$_: " . $frame->{headers}->{$_}; } + keys %{$frame->{headers}}); +} + +sub read_body_file { + my ($r) = @_; + return '' unless $r =~ m/x-body-file: (.*)/; + open FILE, $1 + or return "$!"; + local $/; + my $content = ; + close FILE; + return $content; +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/h2_max_requests.t b/tests/nginx-tests/nginx-tests/h2_request_body_js.t similarity index 50% rename from tests/nginx-tests/nginx-tests/h2_max_requests.t rename to tests/nginx-tests/nginx-tests/h2_request_body_js.t index f3184bfc58..d2455c2b44 100644 --- a/tests/nginx-tests/nginx-tests/h2_max_requests.t +++ b/tests/nginx-tests/nginx-tests/h2_request_body_js.t @@ -3,7 +3,7 @@ # (C) Sergey Kandaurov # (C) Nginx, Inc. -# Tests for HTTP/2 protocol, http2_max_requests directive. +# Tests for HTTP/2 request body with njs subrequest in the body handler. ############################################################################### @@ -36,45 +36,52 @@ events { http { %%TEST_GLOBALS_HTTP%% + js_import test.js; + server { listen 127.0.0.1:8080 http2; server_name localhost; - http2_max_requests 2; + lingering_close off; + + location / { + js_content test.sr_body; + add_header X-Body $request_body; + } - location / { } + location /sr { } } } EOF -$t->write_file('index.html', ''); -$t->run()->plan(5); - -############################################################################### +$t->write_file('test.js', <new(); -my $frames = $s->read(all => [{ sid => $s->new_stream(), fin => 1 }]); +function sr_body(r) { + r.subrequest('/sr', body_fwd_cb); +} -my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; -is($frame->{headers}->{':status'}, 200, 'max requests'); +export default {sr_body}; -$frames = $s->read(all => [{ type => 'GOAWAY' }], wait => 0.5) - unless grep { $_->{type} eq "GOAWAY" } @$frames; +EOF -($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; -is($frame, undef, 'max requests - GOAWAY'); +$t->write_file('sr', 'SEE-THIS'); +$t->try_run('no njs available')->plan(3); -# max requests limited +############################################################################### -my $sid = $s->new_stream(); -$frames = $s->read(all => [{ sid => $sid, fin => 1 }, { type => 'GOAWAY' }]); +my $s = Test::Nginx::HTTP2->new(); +my $sid = $s->new_stream({ body => 'TEST' }); +my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); -($frame) = grep { $_->{type} eq "HEADERS" } @$frames; -is($frame->{headers}->{':status'}, 200, 'max requests limited'); +my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 200, 'status'); +is($frame->{headers}->{'x-body'}, 'TEST', 'request body'); -($frame) = grep { $_->{type} eq "GOAWAY" } @$frames; -ok($frame, 'max requests limited - GOAWAY'); -is($frame->{last_sid}, $sid, 'max requests limited - GOAWAY last stream'); +($frame) = grep { $_->{type} eq "DATA" } @$frames; +is($frame->{data}, 'SEE-THIS', 'response body'); ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/h2_request_body_preread.t b/tests/nginx-tests/nginx-tests/h2_request_body_preread.t index 8edbb4cb42..5e629db527 100644 --- a/tests/nginx-tests/nginx-tests/h2_request_body_preread.t +++ b/tests/nginx-tests/nginx-tests/h2_request_body_preread.t @@ -37,7 +37,7 @@ events { http { %%TEST_GLOBALS_HTTP%% - limit_req_zone $binary_remote_addr zone=req:1m rate=30r/m; + limit_req_zone $binary_remote_addr zone=req:1m rate=20r/m; server { listen 127.0.0.1:8080 http2; @@ -89,7 +89,7 @@ http { EOF $t->write_file('t', ''); -$t->run()->plan(8); +$t->run()->plan(9); ############################################################################### @@ -129,6 +129,21 @@ $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; is($frame->{headers}->{'x-body'}, 'TEST', 'within preread limited'); +# processing request body without END_STREAM in preread + +$sid = $s->new_stream({ path => '/req', body_more => 1, continuation => 1 }); +$s->h2_continue($sid, + { headers => [{ name => 'content-length', value => '8' }]}); + +$s->h2_body('SEE', { body_more => 1 }); +$s->read(all => [{ type => 'WINDOW_UPDATE' }]); + +$s->h2_body('-THIS'); +$frames = $s->read(all => [{ sid => $sid, fin => 1 }]); + +($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{'x-body'}, 'SEE-THIS', 'within preread limited - more'); + # beyond preread size - limited $s = Test::Nginx::HTTP2->new(); diff --git a/tests/nginx-tests/nginx-tests/h2_server_push.t b/tests/nginx-tests/nginx-tests/h2_server_push.t index 3cd5f1cce0..5a8440ba3a 100644 --- a/tests/nginx-tests/nginx-tests/h2_server_push.t +++ b/tests/nginx-tests/nginx-tests/h2_server_push.t @@ -23,7 +23,7 @@ use Test::Nginx::HTTP2; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite gzip/)->plan(42) +my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite gzip/)->plan(54) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -106,6 +106,11 @@ http { return 204; } + location /continuation { + http2_push /push?arg="$args$args$args$args"; + return 204; + } + location /push { return 200 PROMISED; } @@ -409,9 +414,6 @@ gunzip_like($frame->{data}, qr/^PROMISED\Z/, 'gzip - response'); # scheme https -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.15.1'); - $s = Test::Nginx::HTTP2->new(); $sid = $s->new_stream({ headers => [ { name => ':method', value => 'GET', mode => 0 }, @@ -423,7 +425,29 @@ $frames = $s->read(all => [{ sid => 2, fin => 1 }]); ($frame) = grep { $_->{type} eq "PUSH_PROMISE" && $_->{sid} == $sid } @$frames; is($frame->{headers}->{':scheme'}, 'https', 'scheme https'); -} +# CONTINUATION + +$s = Test::Nginx::HTTP2->new(); +$sid = $s->new_stream({ path => '/continuation?x=' . ('X' x 4096) }); +$frames = $s->read(all => [{ sid => 1, fin => 1 }, { sid => 2, fin => 1 }]); + +@$frames = grep { $_->{promised} } @$frames; +is(@$frames, 2, 'continuation - frames'); + +$frame = shift @$frames; +is($frame->{type}, 'PUSH_PROMISE', 'continuation - PUSH_PROMISE'); +is($frame->{length}, 16384, 'continuation - PUSH_PROMISE length'); +is($frame->{flags}, 0, 'continuation - PUSH_PROMISE flags'); +is($frame->{sid}, $sid, 'continuation - PUSH_PROMISE sid'); +is($frame->{promised}, 2, 'continuation - promised stream'); + +$frame = shift @$frames; +is($frame->{type}, 'CONTINUATION', 'continuation - CONTINUATION'); +is($frame->{flags}, 4, 'continuation - CONTINUATION flags'); +is($frame->{headers}->{':authority'}, 'localhost', 'continuation - authority'); +is($frame->{headers}->{':scheme'}, 'http', 'continuation - scheme'); +is($frame->{headers}->{':method'}, 'GET', 'continuation - method'); +like($frame->{headers}->{':path'}, qr!^/push!, 'continuation - path'); ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/h2_server_tokens.t b/tests/nginx-tests/nginx-tests/h2_server_tokens.t index 5bbd70f8a3..766b6dcde2 100644 --- a/tests/nginx-tests/nginx-tests/h2_server_tokens.t +++ b/tests/nginx-tests/nginx-tests/h2_server_tokens.t @@ -92,15 +92,15 @@ $t->run()->plan(12); ############################################################################### -my $re = qr/Tengine\/\d+\.\d+\.\d+/; +my $re = qr/nginx\/\d+\.\d+\.\d+/; like(header_server('/200'), qr/^$re$/, 'http2 tokens default 200'); like(header_server('/404'), qr/^$re$/, 'http2 tokens default 404'); like(body('/404'), qr/$re/, 'http2 tokens default 404 body'); -is(header_server('/off/200'), 'Tengine', 'http2 tokens off 200'); -is(header_server('/off/404'), 'Tengine', 'http2 tokens off 404'); -like(body('/off/404'), qr/Tengine(?!\/)/, 'http2 tokens off 404 body'); +is(header_server('/off/200'), 'nginx', 'http2 tokens off 200'); +is(header_server('/off/404'), 'nginx', 'http2 tokens off 404'); +like(body('/off/404'), qr/nginx(?!\/)/, 'http2 tokens off 404 body'); like(header_server('/on/200'), qr/^$re$/, 'http2 tokens on 200'); like(header_server('/on/404'), qr/^$re$/, 'http2 tokens on 404'); diff --git a/tests/nginx-tests/nginx-tests/h2_ssl.t b/tests/nginx-tests/nginx-tests/h2_ssl.t index 907fb31474..d118f5b9c9 100644 --- a/tests/nginx-tests/nginx-tests/h2_ssl.t +++ b/tests/nginx-tests/nginx-tests/h2_ssl.t @@ -12,6 +12,8 @@ use strict; use Test::More; +use Socket qw/ SOL_SOCKET SO_RCVBUF /; + BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; @@ -23,9 +25,6 @@ use Test::Nginx::HTTP2; select STDERR; $| = 1; select STDOUT; $| = 1; -eval { require IO::Socket::SSL; }; -plan(skip_all => 'IO::Socket::SSL not installed') if $@; - my $t = Test::Nginx->new()->has(qw/http http_ssl http_v2/) ->has_daemon('openssl'); @@ -48,12 +47,23 @@ http { ssl_certificate_key localhost.key; ssl_certificate localhost.crt; + lingering_close off; + location / { } } } EOF +eval { require IO::Socket::SSL; die if $IO::Socket::SSL::VERSION < 1.56; }; +plan(skip_all => 'IO::Socket::SSL version >= 1.56 required') if $@; + +eval { IO::Socket::SSL->can_alpn() or die; }; +plan(skip_all => 'IO::Socket::SSL with OpenSSL ALPN support required') if $@; + +eval { exists &Net::SSLeay::P_alpn_selected or die; }; +plan(skip_all => 'Net::SSLeay with OpenSSL ALPN support required') if $@; + $t->write_file('openssl.conf', <write_file('index.html', ''); $t->write_file('tbig.html', join('', map { sprintf "XX%06dXX", $_ } (1 .. 500000))); @@ -79,19 +90,42 @@ open OLDERR, ">&", \*STDERR; close STDERR; $t->run(); open STDERR, ">&", \*OLDERR; -plan(skip_all => 'no ALPN/NPN negotiation') unless defined getconn(port(8080)); -$t->plan(1); +plan(skip_all => 'no ALPN negotiation') unless defined getconn(); +$t->plan(3); ############################################################################### -# client cancels 2nd stream after HEADERS has been created +SKIP: { +$t->{_configure_args} =~ /LibreSSL ([\d\.]+)/; +skip 'LibreSSL too old', 1 if defined $1 and $1 lt '3.4.0'; +$t->{_configure_args} =~ /OpenSSL ([\d\.]+)/; +skip 'OpenSSL too old', 1 if defined $1 and $1 lt '1.1.0'; + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.4'); + +ok(!get_ssl_socket(['unknown']), 'alpn rejected'); + +} + +} + +like(http_get('/', socket => get_ssl_socket(['http/1.1'])), + qr/200 OK/, 'alpn to HTTP/1.1 fallback'); + +my $s = getconn(['http/1.1', 'h2']); +my $sid = $s->new_stream(); +my $frames = $s->read(all => [{ sid => $sid, fin => 1 }]); +my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames; +is($frame->{headers}->{':status'}, 200, 'alpn to HTTP/2'); + +# client cancels last stream after HEADERS has been created, # while some unsent data was left in the SSL buffer # HEADERS frame may stuck in SSL buffer and won't be sent producing alert -my $s = getconn(port(8080)); -ok($s, 'ssl connection'); - -my $sid = $s->new_stream({ path => '/tbig.html' }); +$s = getconn(['http/1.1', 'h2']); +$s->{socket}->setsockopt(SOL_SOCKET, SO_RCVBUF, 1024*1024) or die $!; +$sid = $s->new_stream({ path => '/tbig.html' }); select undef, undef, undef, 0.2; $s->h2_rst($sid, 8); @@ -106,24 +140,38 @@ $t->stop(); ############################################################################### sub getconn { - my ($port) = @_; - my $s; + my ($alpn) = @_; + $alpn = ['h2'] if !defined $alpn; - eval { - my $sock = Test::Nginx::HTTP2::new_socket($port, SSL => 1, - alpn => 'h2'); - $s = Test::Nginx::HTTP2->new($port, socket => $sock) - if $sock->alpn_selected(); - }; + my $sock = get_ssl_socket($alpn); + my $s = Test::Nginx::HTTP2->new(undef, socket => $sock) + if $sock->alpn_selected(); +} - return $s if defined $s; +sub get_ssl_socket { + my ($alpn) = @_; + my $s; eval { - my $sock = Test::Nginx::HTTP2::new_socket($port, SSL => 1, - npn => 'h2'); - $s = Test::Nginx::HTTP2->new($port, socket => $sock) - if $sock->next_proto_negotiated(); + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(8); + $s = IO::Socket::SSL->new( + Proto => 'tcp', + PeerAddr => '127.0.0.1', + PeerPort => port(8080), + SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), + SSL_alpn_protocols => $alpn, + SSL_error_trap => sub { die $_[1] } + ); + alarm(0); }; + alarm(0); + + if ($@) { + log_in("died: $@"); + return undef; + } return $s; } diff --git a/tests/nginx-tests/nginx-tests/h2_ssl_proxy_cache.t b/tests/nginx-tests/nginx-tests/h2_ssl_proxy_cache.t index 74dcfb220b..5edd43b54d 100644 --- a/tests/nginx-tests/nginx-tests/h2_ssl_proxy_cache.t +++ b/tests/nginx-tests/nginx-tests/h2_ssl_proxy_cache.t @@ -51,6 +51,7 @@ http { ssl_certificate localhost.crt; send_timeout 1s; + lingering_close off; location / { proxy_pass http://127.0.0.1:8081; @@ -117,11 +118,6 @@ select undef, undef, undef, 0.2; $t->stop(); -# "aio_write" is used to produce "open socket ... left in connection" alerts. - -$t->todo_alerts() if $t->read_file('nginx.conf') =~ /aio_write on/ - and $t->read_file('nginx.conf') =~ /aio threads/ and $^O eq 'linux'; - ############################################################################### sub getconn { diff --git a/tests/nginx-tests/nginx-tests/h2_trailers.t b/tests/nginx-tests/nginx-tests/h2_trailers.t index 2b0098dbcc..88f7415680 100644 --- a/tests/nginx-tests/nginx-tests/h2_trailers.t +++ b/tests/nginx-tests/nginx-tests/h2_trailers.t @@ -44,8 +44,6 @@ http { add_trailer X-Var $host; } - http2_max_field_size 256k; - location /continuation { # many trailers to send in parts add_trailer X-LongHeader $arg_h; @@ -108,7 +106,7 @@ is($frame->{flags}, 5, 'no data - trailer flags'); # CONTINUATION in response trailers $s = Test::Nginx::HTTP2->new(); -$sid = $s->new_stream({ path => '/continuation?h=' . 'x' x 2**12 }); +$sid = $s->new_stream({ path => '/continuation?h=' . 'x' x 4000 }); $frames = $s->read(all => [{ sid => $sid, type => 'CONTINUATION' }]); @$frames = grep { $_->{type} =~ "HEADERS|CONTINUATION|DATA" } @$frames; diff --git a/tests/nginx-tests/nginx-tests/http_absolute_redirect.t b/tests/nginx-tests/nginx-tests/http_absolute_redirect.t new file mode 100644 index 0000000000..ff3bb13b92 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/http_absolute_redirect.t @@ -0,0 +1,216 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for absolute_redirect directive and Location escaping. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + absolute_redirect off; + + server { + listen 127.0.0.1:8080; + server_name on; + + absolute_redirect on; + error_page 400 /return301; + + location / { } + + location /auto/ { + proxy_pass http://127.0.0.1:8080; + } + + location "/auto sp/" { + proxy_pass http://127.0.0.1:8080; + } + + location /return301 { + return 301 /redirect; + } + + location /return301/name { + return 301 /redirect; + server_name_in_redirect on; + } + + location /return301/port { + return 301 /redirect; + port_in_redirect off; + } + + location /i/ { + alias %%TESTDIR%%/; + } + } + + server { + listen 127.0.0.1:8080; + server_name off; + + location / { } + + location /auto/ { + proxy_pass http://127.0.0.1:8080; + } + + location "/auto sp/" { + proxy_pass http://127.0.0.1:8080; + } + + location '/auto "#%<>?\^`{|}/' { + proxy_pass http://127.0.0.1:8080; + } + + location /return301 { + return 301 /redirect; + } + + location /i/ { + alias %%TESTDIR%%/; + } + } +} + +EOF + +mkdir($t->testdir() . '/dir'); +mkdir($t->testdir() . '/dir sp'); + +$t->run()->plan(23); + +############################################################################### + +my $p = port(8080); + +like(get('on', '/dir'), qr!Location: http://on:$p/dir/\x0d?$!m, 'directory'); +like(get('on', '/i/dir'), qr!Location: http://on:$p/i/dir/\x0d?$!m, + 'directory alias'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +like(get('on', '/dir%20sp'), qr!Location: http://on:$p/dir%20sp/\x0d?$!m, + 'directory escaped'); +like(get('on', '/dir%20sp?a=b'), + qr!Location: http://on:$p/dir%20sp/\?a=b\x0d?$!m, + 'directory escaped args'); + +} + +like(get('on', '/auto'), qr!Location: http://on:$p/auto/\x0d?$!m, 'auto'); +like(get('on', '/auto?a=b'), qr!Location: http://on:$p/auto/\?a=b\x0d?$!m, + 'auto args'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +like(get('on', '/auto%20sp'), qr!Location: http://on:$p/auto%20sp/\x0d?$!m, + 'auto escaped'); +like(get('on', '/auto%20sp?a=b'), + qr!Location: http://on:$p/auto%20sp/\?a=b\x0d?$!m, + 'auto escaped args'); + +} + +like(get('on', '/return301'), qr!Location: http://on:$p/redirect\x0d?$!m, + 'return'); + +like(get('host', '/return301/name'), qr!Location: http://on:$p/redirect\x0d?!m, + 'server_name_in_redirect on'); +like(get('host', '/return301'), qr!Location: http://host:$p/redirect\x0d?$!m, + 'server_name_in_redirect off - using host'); +my $ph = IO::Socket::INET->new("127.0.0.1:$p")->peerhost(); +like(get('.', '/return301'), qr!Location: http://$ph:$p/redirect\x0d?$!m, + 'invalid host - using local sockaddr'); +like(get('host', '/return301/port'), qr!Location: http://host/redirect\x0d?$!m, + 'port_in_redirect off'); + +like(get('off', '/dir'), qr!Location: /dir/\x0d?$!m, 'off directory'); +like(get('off', '/i/dir'), qr!Location: /i/dir/\x0d?$!m, 'off directory alias'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +like(get('off', '/dir%20sp'), qr!Location: /dir%20sp/\x0d?$!m, + 'off directory escaped'); +like(get('off', '/dir%20sp?a=b'), qr!Location: /dir%20sp/\?a=b\x0d?$!m, + 'off directory escaped args'); + +} + +like(get('off', '/auto'), qr!Location: /auto/\x0d?$!m, 'off auto'); +like(get('off', '/auto?a=b'), qr!Location: /auto/\?a=b\x0d?$!m, + 'off auto args'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +like(get('off', '/auto%20sp'), qr!Location: /auto%20sp/\x0d?$!m, + 'auto escaped'); +like(get('off', '/auto%20sp?a=b'), qr!Location: /auto%20sp/\?a=b\x0d?$!m, + 'auto escaped args'); + +} + +like(get('off', '/return301'), qr!Location: /redirect\x0d?$!m, 'off return'); + +# per RFC 3986, these characters are not allowed unescaped: +# %00-%1F, %7F-%FF, " ", """, "<", ">", "\", "^", "`", "{", "|", "}" +# additionally, all characters in ESCAPE_URI: "?", "%", "#" + +SKIP: { +skip 'win32', 1 if $^O eq 'MSWin32'; + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.1'); + +like(get('off', '/auto%20%22%23%25%3C%3E%3F%5C%5E%60%7B%7C%7D'), + qr!Location: /auto%20%22%23%25%3C%3E%3F%5C%5E%60%7B%7C%7D/\x0d?$!m, + 'auto escaped strict'); + +} + +} + +############################################################################### + +sub get { + my ($host, $uri) = @_; + http(< 'no external file found') @@ -209,7 +209,7 @@ symlink($extfile, "$d/cached/link"); ############################################################################### SKIP: { -skip 'cannot test under symlink', 25 if $d ne realpath($d); +skip 'cannot test under symlink', 25 if $d ne realpath($d) or $^O eq 'netbsd'; like(http_get_host('s1', '/link'), qr!200 OK!, 'static (off, same uid)'); like(http_get_host('s1', '/link2'), qr!200 OK!, 'static (off, other uid)'); diff --git a/tests/nginx-tests/nginx-tests/http_error_page.t b/tests/nginx-tests/nginx-tests/http_error_page.t index 35f46da7a4..ce947f027e 100644 --- a/tests/nginx-tests/nginx-tests/http_error_page.t +++ b/tests/nginx-tests/nginx-tests/http_error_page.t @@ -21,7 +21,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http proxy rewrite/)->plan(7) +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/)->plan(9) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -63,6 +63,20 @@ http { return 302 "http://example.com/"; } + location /error302return302args { + error_page 302 /return302args?1; + return 302 "first"; + } + + location /error302return302varargs { + error_page 302 /return302args?$arg_a; + return 302 "first"; + } + + location /return302args { + return 302 "http://example.com/$args"; + } + location /error302rewrite { error_page 302 /rewrite; return 302 "first"; @@ -116,6 +130,14 @@ like(http_get('/error302return302text'), qr{HTTP/1.1 302(?!.*Location: first).*Location: http://example.com/}ms, 'error 302 return 302 text - old location cleared'); +like(http_get('/error302return302args'), + qr{HTTP/1.1 302(?!.*Location: first).*Location: http://example.com/1}ms, + 'error 302 return 302 args - old location cleared'); + +like(http_get('/error302return302varargs?a=2'), + qr{HTTP/1.1 302(?!.*Location: first).*Location: http://example.com/2}ms, + 'error 302 return 302 var args - old location cleared'); + like(http_get('/error302rewrite'), qr{HTTP/1.1 302(?!.*Location: first).*Location: http://example.com/}ms, 'error 302 rewrite - old location cleared'); diff --git a/tests/nginx-tests/nginx-tests/http_expect_100_continue.t b/tests/nginx-tests/nginx-tests/http_expect_100_continue.t index e5d9d6d0ad..636b05745c 100644 --- a/tests/nginx-tests/nginx-tests/http_expect_100_continue.t +++ b/tests/nginx-tests/nginx-tests/http_expect_100_continue.t @@ -94,7 +94,7 @@ like(http_100_request('/', '1.1', 'token=param'), qr/ 417 /, sub http_100_request { my ($url, $version, $value) = @_; $value = '100-continue' unless defined $value; - my $r = http(<new()->has(qw/http rewrite/)->plan(10) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + connection_pool_size 128; + client_header_buffer_size 128; + + server { + listen 127.0.0.1:8080; + server_name five; + + large_client_header_buffers 5 256; + + return 204; + } + + server { + listen 127.0.0.1:8080; + server_name ten; + + large_client_header_buffers 10 256; + + return 204; + } + + server { + listen 127.0.0.1:8080; + server_name one; + + large_client_header_buffers 1 256; + + return 204; + } + + server { + listen 127.0.0.1:8080; + server_name foo; + + large_client_header_buffers 5 256; + + add_header X-URI $uri; + add_header X-Foo $http_x_foo; + return 204; + } +} + +EOF + +$t->run(); + +############################################################################### + +TODO: { +todo_skip 'overflow', 2 unless $ENV{TEST_NGINX_UNSAFE}; + +# if hc->busy is allocated before the virtual server is selected, +# and then additional buffers are allocated in a virtual server with larger +# number of buffers configured, hc->busy will be overflowed + +like(http( + "GET / HTTP/1.0" . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "Host: ten" . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + CRLF +), qr/204|400/, 'additional buffers in virtual server'); + +# for pipelined requests large header buffers are saved to hc->free; +# it sized for number of buffers in the current virtual server, but +# saves previously allocated buffers, and there may be more buffers if +# allocatad before the virtual server was selected + +like(http( + "GET / HTTP/1.1" . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "Host: one" . CRLF . + CRLF . + "GET / HTTP/1.1" . CRLF . + "Host: one" . CRLF . + "Connection: close" . CRLF . + CRLF +), qr/204/, 'pipelined with too many buffers'); + +} + +# check if long header and long request lines are correctly returned +# when nginx allocates a long header buffer + +like(http( + "GET / HTTP/1.0" . CRLF . + "Host: foo" . CRLF . + "X-Foo: foo" . ("1234567890" x 20) . "bar" . CRLF . + CRLF +), qr/X-Foo: foo(1234567890){20}bar/, 'long header'); + +like(http( + "GET /foo" . ("1234567890" x 20) . "bar HTTP/1.0" . CRLF . + "Host: foo" . CRLF . + CRLF +), qr!X-URI: /foo(1234567890){20}bar!, 'long request line'); + +# the same as the above, but with pipelining, so there is a buffer +# allocated in the previous request + +like(http( + "GET / HTTP/1.1" . CRLF . + "Host: foo" . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + CRLF . + "GET / HTTP/1.1" . CRLF . + "Host: foo" . CRLF . + "Connection: close" . CRLF . + "X-Foo: foo" . ("1234567890" x 20) . "bar" . CRLF . + CRLF +), qr/X-Foo: foo(1234567890){20}bar/, 'long header after pipelining'); + +like(http( + "GET / HTTP/1.1" . CRLF . + "Host: foo" . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + CRLF . + "GET /foo" . ("1234567890" x 20) . "bar HTTP/1.1" . CRLF . + "Host: foo" . CRLF . + "Connection: close" . CRLF . + CRLF +), qr!X-URI: /foo(1234567890){20}bar!, 'long request line after pipelining'); + +# the same as the above, but with keepalive; this ensures that previously +# allocated buffers are properly cleaned up when we set keepalive handler + +like(http( + "GET / HTTP/1.1" . CRLF . + "Host: foo" . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + CRLF, +sleep => 0.1, body => + "GET / HTTP/1.1" . CRLF . + "Host: foo" . CRLF . + "Connection: close" . CRLF . + "X-Foo: foo" . ("1234567890" x 20) . "bar" . CRLF . + CRLF +), qr/X-Foo: foo(1234567890){20}bar/, 'long header after keepalive'); + +like(http( + "GET / HTTP/1.1" . CRLF . + "Host: foo" . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + CRLF, +sleep => 0.1, body => + "GET /foo" . ("1234567890" x 20) . "bar HTTP/1.1" . CRLF . + "Host: foo" . CRLF . + "Connection: close" . CRLF . + CRLF +), qr!X-URI: /foo(1234567890){20}bar!, 'long request line after keepalive'); + +# the same as the above, but with pipelining and then keepalive; +# this ensures that previously allocated buffers are properly cleaned +# up when we set keepalive handler, including hc->free + +like(http( + "GET / HTTP/1.1" . CRLF . + "Host: foo" . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + CRLF . + "GET / HTTP/1.1" . CRLF . + "Host: foo" . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + CRLF, +sleep => 0.1, body => + "GET / HTTP/1.1" . CRLF . + "Host: foo" . CRLF . + "Connection: close" . CRLF . + "X-Foo: foo" . ("1234567890" x 20) . "bar" . CRLF . + CRLF +), qr/X-Foo: foo(1234567890){20}bar/, 'long header after both'); + +like(http( + "GET / HTTP/1.1" . CRLF . + "Host: foo" . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + CRLF . + "GET / HTTP/1.1" . CRLF . + "Host: foo" . CRLF . + "X-Foo: " . ("1234567890" x 20) . CRLF . + CRLF, +sleep => 0.1, body => + "GET /foo" . ("1234567890" x 20) . "bar HTTP/1.1" . CRLF . + "Host: foo" . CRLF . + "Connection: close" . CRLF . + CRLF +), qr!X-URI: /foo(1234567890){20}bar!, 'long request line after both'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/http_headers_multi.t b/tests/nginx-tests/nginx-tests/http_headers_multi.t new file mode 100644 index 0000000000..eb237a5eda --- /dev/null +++ b/tests/nginx-tests/nginx-tests/http_headers_multi.t @@ -0,0 +1,313 @@ +#!/usr/bin/perl + +# (C) Maxim Dounin +# (C) Nginx, Inc. + +# Tests for handling of multiple http headers and access via variables. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite proxy/)->plan(42); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + add_header X-Forwarded-For $http_x_forwarded_for; + add_header X-Cookie $http_cookie; + add_header X-Foo $http_foo; + + add_header X-Cookie-Foo $cookie_foo; + add_header X-Cookie-Bar $cookie_bar; + add_header X-Cookie-Bazz $cookie_bazz; + + return 204; + } + + location /s { + add_header Cache-Control foo; + add_header Cache-Control bar; + add_header Cache-Control bazz; + + add_header Link foo; + add_header Link bar; + add_header Link bazz; + + add_header Foo foo; + add_header Foo bar; + add_header Foo bazz; + + add_header X-Sent-CC $sent_http_cache_control; + add_header X-Sent-Link $sent_http_link; + add_header X-Sent-Foo $sent_http_foo; + + return 204; + } + + location /t { + add_trailer Foo foo; + add_trailer Foo bar; + add_trailer Foo bazz; + add_trailer X-Sent-Trailer-Foo $sent_trailer_foo; + + return 200 ""; + } + + location /v { + add_header X-Forwarded-For $http_x_forwarded_for; + add_header X-Cookie $http_cookie; + + add_header X-HTTP-Host $http_host; + add_header X-User-Agent $http_user_agent; + add_header X-Referer $http_referer; + add_header X-Via $http_via; + + add_header X-Content-Length $content_length; + add_header X-Content-Type $content_type; + add_header X-Host $host; + add_header X-Remote-User $remote_user; + + return 204; + } + + location /d { + return 204; + } + + location /u { + add_header X-Upstream-Set-Cookie $upstream_http_set_cookie; + add_header X-Upstream-Bar $upstream_http_bar; + + add_header X-Upstream-Cookie-Foo $upstream_cookie_foo; + add_header X-Upstream-Cookie-Bar $upstream_cookie_bar; + add_header X-Upstream-Cookie-Bazz $upstream_cookie_bazz; + + proxy_pass http://127.0.0.1:8080/backend; + } + + location /backend { + add_header Set-Cookie foo=1; + add_header Set-Cookie bar=2; + add_header Set-Cookie bazz=3; + add_header Bar foo; + add_header Bar bar; + add_header Bar bazz; + return 204; + } + } +} + +EOF + +$t->run(); + +############################################################################### + +# combining multiple headers: +# +# $http_cookie, $http_x_forwarded_for, $sent_http_cache_control, +# and $sent_http_link with special handling, other headers with +# general handling + +# request headers, $http_* + +like(get('/', map { "X-Forwarded-For: $_" } qw/ foo bar bazz /), + qr/X-Forwarded-For: foo, bar, bazz/, 'multi $http_x_forwarded_for'); +like(get('/', 'Cookie: foo=1', 'Cookie: bar=2', 'Cookie: bazz=3'), + qr/X-Cookie: foo=1; bar=2; bazz=3/, 'multi $http_cookie'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like(get('/', 'Foo: foo', 'Foo: bar', 'Foo: bazz'), + qr/X-Foo: foo, bar, bazz/, 'multi $http_foo'); + +} + +# request cookies, $cookie_* + +my $r = get('/', 'Cookie: foo=1', 'Cookie: bar=2', 'Cookie: bazz=3'); + +like($r, qr/X-Cookie-Foo: 1/, '$cookie_foo'); +like($r, qr/X-Cookie-Bar: 2/, '$cookie_bar'); +like($r, qr/X-Cookie-Bazz: 3/, '$cookie_bazz'); + +# response headers, $http_* + +$r = get('/s'); + +like($r, qr/X-Sent-CC: foo, bar, bazz/, 'multi $sent_http_cache_control'); +like($r, qr/X-Sent-Link: foo, bar, bazz/, 'multi $sent_http_link'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like($r, qr/X-Sent-Foo: foo, bar, bazz/, 'multi $sent_http_foo'); + +} + +# upstream response headers, $upstream_http_* + +$r = get('/u'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like($r, qr/X-Upstream-Set-Cookie: foo=1, bar=2, bazz=3/, + 'multi $upstream_http_set_cookie'); +like($r, qr/X-Upstream-Bar: foo, bar, bazz/, 'multi $upstream_http_bar'); + +} + +# upstream response cookies, $upstream_cookie_* + +like($r, qr/X-Upstream-Cookie-Foo: 1/, '$upstream_cookie_foo'); +like($r, qr/X-Upstream-Cookie-Bar: 2/, '$upstream_cookie_bar'); +like($r, qr/X-Upstream-Cookie-Bazz: 3/, '$upstream_cookie_bazz'); + +# response trailers, $sent_trailer_* + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like(get('/t'), qr/X-Sent-Trailer-Foo: foo, bar, bazz/, + 'multi $sent_trailer_foo'); + +} + +# various variables for request headers: +# +# $http_host, $http_user_agent, $http_referer +# multiple Host, User-Agent, Referer headers are invalid, but we currently +# reject only requests with multiple Host headers +# +# $http_via, $http_x_forwarded_for, $http_cookie +# multiple headers are valid + +like(get('/v'), qr/X-HTTP-Host: localhost/, '$http_host'); +like(get('/v', 'Host: foo', 'Host: bar'), + qr/400 Bad/, 'duplicate host rejected'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like(get('/v', 'User-Agent: foo', 'User-Agent: bar'), + qr/X-User-Agent: foo, bar/, 'multi $http_user_agent (invalid)'); +like(get('/v', 'Referer: foo', 'Referer: bar'), + qr/X-Referer: foo, bar/, 'multi $http_referer (invalid)'); +like(get('/v', 'Via: foo', 'Via: bar', 'Via: bazz'), + qr/X-Via: foo, bar, bazz/, 'multi $http_via'); + +} + +like(get('/v', 'Cookie: foo', 'Cookie: bar', 'Cookie: bazz'), + qr/X-Cookie: foo; bar; bazz/, 'multi $http_cookie'); +like(get('/v', 'X-Forwarded-For: foo', 'X-Forwarded-For: bar', + 'X-Forwarded-For: bazz'), + qr/X-Forwarded-For: foo, bar, bazz/, 'multi $http_x_forwarded_for'); + +# other variables related to request headers: +# +# $content_length, $content_type, $host, $remote_user + +like(get('/v', 'Content-Length: 0'), + qr/X-Content-Length: 0/, '$content_length'); +like(get('/v', 'Content-Length: 0', 'Content-Length: 0'), + qr/400 Bad/, 'duplicate Content-Length rejected'); + +like(get('/v', 'Content-Type: foo'), + qr/X-Content-Type: foo/, '$content_type'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like(get('/v', 'Content-Type: foo', 'Content-Type: bar'), + qr/X-Content-Type: foo, bar/, 'multi $content_type (invalid)'); + +} + +like(http("GET /v HTTP/1.0" . CRLF . CRLF), + qr/X-Host: localhost/, '$host from server_name'); +like(http("GET /v HTTP/1.0" . CRLF . "Host: foo" . CRLF . CRLF), + qr/X-Host: foo/, '$host'); +like(http("GET /v HTTP/1.0" . CRLF . "Host: foo" . CRLF . + "Host: bar" . CRLF . CRLF), + qr/400 Bad/, 'duplicate host rejected'); + +like(get('/v', 'Authorization: Basic dXNlcjpzZWNyZXQ='), + qr/X-Remote-User: user/, '$remote_user'); +like(get('/v', 'Authorization: Basic dXNlcjpzZWNyZXQ=', + 'Authorization: Basic dXNlcjpzZWNyZXQ='), + qr/400 Bad/, 'duplicate authorization rejected'); + +# request headers required to be unique: +# +# Host, If-Modified-Since, If-Unmodified-Since, If-Match, If-None-Match, +# Content-Length, Content-Range, If-Range, Transfer-Encoding, Expect, +# Authorization + +like(get('/d', 'Host: foo', 'Host: bar'), + qr/400 Bad/, 'duplicate Host rejected'); +like(get('/d', 'If-Modified-Since: foo', 'If-Modified-Since: bar'), + qr/400 Bad/, 'duplicate If-Modified-Since rejected'); +like(get('/d', 'If-Unmodified-Since: foo', 'If-Unmodified-Since: bar'), + qr/400 Bad/, 'duplicate If-Unmodified-Since rejected'); +like(get('/d', 'If-Match: foo', 'If-Match: bar'), + qr/400 Bad/, 'duplicate If-Match rejected'); +like(get('/d', 'If-None-Match: foo', 'If-None-Match: bar'), + qr/400 Bad/, 'duplicate If-None-Match rejected'); +like(get('/d', 'Content-Length: 0', 'Content-Length: 0'), + qr/400 Bad/, 'duplicate Content-Length rejected'); +like(get('/d', 'Content-Range: foo', 'Content-Range: bar'), + qr/400 Bad/, 'duplicate Content-Range rejected'); +like(get('/d', 'If-Range: foo', 'If-Range: bar'), + qr/400 Bad/, 'duplicate If-Range rejected'); +like(get('/d', 'Transfer-Encoding: foo', 'Transfer-Encoding: bar'), + qr/400 Bad/, 'duplicate Transfer-Encoding rejected'); +like(get('/d', 'Expect: foo', 'Expect: bar'), + qr/400 Bad/, 'duplicate Expect rejected'); +like(get('/d', 'Authorization: foo', 'Authorization: bar'), + qr/400 Bad/, 'duplicate Authorization rejected'); + +############################################################################### + +sub get { + my ($url, @headers) = @_; + return http( + "GET $url HTTP/1.1" . CRLF . + 'Host: localhost' . CRLF . + 'Connection: close' . CRLF . + join(CRLF, @headers) . CRLF . CRLF + ); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/http_host.t b/tests/nginx-tests/nginx-tests/http_host.t index 42d205b501..989a9fe5c0 100644 --- a/tests/nginx-tests/nginx-tests/http_host.t +++ b/tests/nginx-tests/nginx-tests/http_host.t @@ -15,14 +15,14 @@ use Test::More; BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; -use Test::Nginx; +use Test::Nginx qw/ :DEFAULT http_content /; ############################################################################### select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http rewrite/)->plan(35); +my $t = Test::Nginx->new()->has(qw/http rewrite/)->plan(37); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -52,10 +52,9 @@ $t->run(); ############################################################################### - is(http_host_header('www.abcd-ef.g02.xyz'), 'www.abcd-ef.g02.xyz', 'domain w/o port (host header)'); -is(http_host_header('abcd-ef.g02.xyz:8080'), 'abcd-ef.g02.xyz', +is(http_host_header('abcd-ef.g02.xyz:' . port(8080)), 'abcd-ef.g02.xyz', 'domain w/port (host header)'); is(http_absolute_path('abcd-ef.g02.xyz'), 'abcd-ef.g02.xyz', @@ -170,6 +169,15 @@ is(http_absolute_path( is(http_host_header('123.40.56.78:9000:80'), '123.40.56.78', 'double port hack'); +like(http_host_header("localhost\nHost: again", 1), qr/ 400 /, 'host repeat'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.1'); + +like(http_host_header("localhost\x02", 1), qr/ 400 /, 'control'); + +} + ############################################################################### sub http_host_header { @@ -179,7 +187,7 @@ GET / HTTP/1.0 Host: $host EOF - return ($all ? $r : Test::Nginx::http_content($r)); + return ($all ? $r : http_content($r)); } sub http_absolute_path { @@ -189,5 +197,7 @@ GET http://$host/ HTTP/1.0 Host: localhost EOF - return ($all ? $r : Test::Nginx::http_content($r)); + return ($all ? $r : http_content($r)); } + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/http_include.t b/tests/nginx-tests/nginx-tests/http_include.t new file mode 100644 index 0000000000..8d36a17be9 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/http_include.t @@ -0,0 +1,102 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for include directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite proxy access/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + upstream u { + include ups.conf; + } + + server { + listen 127.0.0.1:8080; + server_name localhost; + + if ($arg_s) { + include sif.conf; + } + + location / { + if ($arg_l) { + include lif.conf; + } + } + + location /lmt { + limit_except GET { + include lmt.conf; + } + } + + location /proxy { + add_header X-IP $upstream_addr always; + proxy_pass http://u/backend; + } + + location /backend { } + } +} + +EOF + +my $p = port(8080); + +$t->write_file('sif.conf', 'return 200 SIF;'); +$t->write_file('lif.conf', 'return 200 LIF;'); +$t->write_file('lmt.conf', 'deny all;'); +$t->write_file('ups.conf', "server 127.0.0.1:$p;"); + +$t->run()->plan(5); + +############################################################################### + +like(http_get('/?s=1'), qr/SIF/, 'include in server if'); +like(http_get('/?l=1'), qr/LIF/, 'include in location if'); +like(http_post('/lmt'), qr/ 403 /, 'include in limit_except'); +like(http_get('/proxy'), qr/X-IP: 127.0.0.1:$p/, 'include in upstream'); + +unlike(http_get('/'), qr/ 200 /, 'no include'); + +############################################################################### + +sub http_post { + my ($uri) = @_; + http(<new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + log_format test1 $sent_http_connection; + log_format test2 $sent_http_keep_alive; + access_log %%TESTDIR%%/test1.log test1 if=$arg_l; + access_log %%TESTDIR%%/test2.log test2 if=$arg_l; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + keepalive_requests 2; + keepalive_timeout 1 9; + + add_header X-Conn $connection_requests:$connection_time; + + location / { } + location /r { + keepalive_requests 4; + keepalive_timeout 30s; + } + + location /time { + keepalive_requests 100; + keepalive_timeout 75s; + keepalive_time 1s; + } + + location /safari { + keepalive_disable safari; + } + + location /none { + keepalive_disable none; + } + + location /zero { + keepalive_timeout 0; + } + } +} + +EOF + +$t->write_file('index.html', ''); +$t->write_file('r', ''); +$t->write_file('time', ''); +$t->write_file('safari', ''); +$t->write_file('none', ''); +$t->write_file('zero', ''); +$t->run()->plan(21); + +############################################################################### + +# keepalive_requests + +like(http_keepalive('/'), qr/Connection: keep-alive/, 'keepalive request'); +is(count_keepalive(http_keepalive('/?l=ok', req => 2)), 1, 'keepalive limit'); +is(count_keepalive(http_keepalive('/r', req => 3)), 3, 'keepalive merge'); +is(count_keepalive(http_keepalive('/r', req => 5)), 3, 'keepalive merge limit'); + +# keepalive_disable + +like(http_keepalive('/', method => 'POST', ua => "MSIE 5.0"), + qr/Connection: close/, 'keepalive disable msie6'); +like(http_keepalive('/', ua => "MSIE 5.0"), qr/Connection: keep-alive/, + 'keepalive disable msie6 GET'); +like(http_keepalive('/', method => 'POST', ua => "MSIE 7.0"), + qr/Connection: keep-alive/, 'keepalive disable msie6 modern'); +like(http_keepalive('/', ua => "Mac OS X Safari/7534.48.3"), + qr/Connection: keep-alive/, 'keepalive disable msie6 safari'); +like(http_keepalive('/safari', ua => "Mac OS X Safari/7534.48.3"), + qr/Connection: close/, 'keepalive disable safari'); +like(http_keepalive('/none', method => 'POST', ua => "MSIE 5.0"), + qr/Connection: keep-alive/, 'keepalive disable none'); + +# keepalive_timeout + +my $r = http_keepalive('/', req => 2, sleep => 2.1); +is(count_keepalive($r), 1, 'keepalive timeout request'); +like($r, qr/Keep-Alive: timeout=9/, 'keepalive timeout header'); + +like(http_keepalive('/zero'), qr/Connection: close/, 'keepalive timeout 0'); + +# keepalive_time + +$r = http_keepalive('/time', req => 3); +is(() = $r =~ /(200 OK)/g, 3, 'keepalive time requests'); +unlike($r, qr/Connection: close/, 'keepalive time connection'); + +$r = http_keepalive('/time', req => 3, sleep => 1.2); +is(() = $r =~ /(200 OK)/g, 2, 'keepalive time limit requests'); +like($r, qr/Connection: close/, 'keepalive time limit connection'); + +like($r, qr/X-Conn: 1:0.*X-Conn: 2:[^0]/s, 'keepalive time limit variables'); + +# cancel keepalive on EOF while discarding body + +my $s = http(< 1); +POST /r HTTP/1.1 +Host: localhost +Content-Length: 10 + +EOF + +read_keepalive($s); +shutdown($s, 1); + +ok(IO::Select->new($s)->can_read(3), 'EOF in discard body'); + +$t->stop(); + +TODO: { +local $TODO = 'not yet'; + +is($t->read_file('test1.log'), "keep-alive\nclose\n", 'sent_http_connection'); +is($t->read_file('test2.log'), "timeout=9\n-\n", 'sent_http_keep_alive'); + +} + +############################################################################### + +sub http_keepalive { + my ($url, %opts) = @_; + my $total = ''; + + $opts{ua} = $opts{ua} || ''; + $opts{req} = $opts{req} || 1; + $opts{sleep} = $opts{sleep} || 0; + $opts{method} = $opts{method} || 'GET'; + + local $SIG{PIPE} = 'IGNORE'; + + my $s = http('', start => 1); + + for my $i (1 .. $opts{req}) { + + my $sleep = ($i == 1 ? $opts{sleep} : 0); + + http(< $s, start => 1, sleep => $sleep); +$opts{method} $url HTTP/1.1 +Host: localhost +User-Agent: $opts{ua} + +EOF + + $total .= read_keepalive($s); + } + + return $total; +} + +sub read_keepalive { + my ($s) = @_; + my $data = ''; + + while (IO::Select->new($s)->can_read(3)) { + sysread($s, my $buffer, 4096) or last; + $data .= $buffer; + last if $data =~ /^\x0d\x0a/ms; + } + + log_in($data); + return $data; +} + +sub count_keepalive { + my ($str) = @_; + return $str =~ s/Connection: keep-alive//g; +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/http_keepalive_shutdown.t b/tests/nginx-tests/nginx-tests/http_keepalive_shutdown.t new file mode 100644 index 0000000000..27e960a74d --- /dev/null +++ b/tests/nginx-tests/nginx-tests/http_keepalive_shutdown.t @@ -0,0 +1,77 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http keepalive connections on worker shutdown. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx qw/ :DEFAULT http_end /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http limit_req/)->plan(1); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + limit_req_zone $binary_remote_addr zone=one:1m rate=1r/s; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + limit_req zone=one burst=5; + } + } +} + +EOF + +$t->write_file('test.html', 'XtestX'); +$t->run(); + +############################################################################### + +local $TODO = 'not yet' unless $t->has_version('1.21.6'); + +# signaling on graceful shutdown to client that keepalive connection is closing + +my $s = http(< 1); +HEAD /test.html HTTP/1.1 +Host: localhost + +HEAD /test.html HTTP/1.1 +Host: localhost + +EOF + +select undef, undef, undef, 0.1; + +$t->stop(); + +like(http_end($s), qr/Connection: close/, 'connection close on exit'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/http_listen.t b/tests/nginx-tests/nginx-tests/http_listen.t new file mode 100644 index 0000000000..e63f27cc44 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/http_listen.t @@ -0,0 +1,97 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for listen port ranges. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + listen 127.0.0.1:%%PORT_8082%%-%%PORT_8083%%; + listen %%PORT_8085%%-%%PORT_8086%%; + listen [::1]:%%PORT_8085%%-%%PORT_8086%%; + server_name localhost; + + location / { + proxy_pass http://$arg_b/t; + } + + location /t { + return 200 $server_addr:$server_port; + } + } + + # catch out of range + + server { + listen 127.0.0.1:8081; + listen 127.0.0.1:8084; + listen 127.0.0.1:8087; + listen [::1]:%%PORT_8084%%; + listen [::1]:%%PORT_8087%%; + server_name localhost; + } +} + +EOF + +my $p0 = port(8080); my $p3 = port(8083); my $p6 = port(8086); +my $p1 = port(8081); my $p4 = port(8084); my $p7 = port(8087); +my $p2 = port(8082); my $p5 = port(8085); + +plan(skip_all => 'listen on wildcard address') + unless $ENV{TEST_NGINX_UNSAFE}; + +plan(skip_all => 'no requested ranges') + if "$p0$p1$p2$p3$p4$p5$p6$p7" ne "80808081808280838084808580868087"; + +$t->run()->plan(12); + +############################################################################### + +like(http_get("/?b=127.0.0.1:$p0"), qr/127.0.0.1:$p0/, 'single'); +unlike(http_get("/?b=127.0.0.1:$p1"), qr/127.0.0.1:$p1/, 'out of range 1'); +like(http_get("/?b=127.0.0.1:$p2"), qr/127.0.0.1:$p2/, 'range 1'); +like(http_get("/?b=127.0.0.1:$p3"), qr/127.0.0.1:$p3/, 'range 2'); +unlike(http_get("/?b=127.0.0.1:$p4"), qr/127.0.0.$p4/, 'out of range 2'); +like(http_get("/?b=127.0.0.1:$p5"), qr/127.0.0.1:$p5/, 'wildcard range 1'); +like(http_get("/?b=127.0.0.1:$p6"), qr/127.0.0.1:$p6/, 'wildcard range 2'); +unlike(http_get("/?b=127.0.0.1:$p7"), qr/127.0.0.1:$p7/, 'out of range 3'); + +unlike(http_get("/?b=[::1]:$p4"), qr/::1:$p4/, 'out of range 4'); +like(http_get("/?b=[::1]:$p5"), qr/::1:$p5/, 'ipv6 range 1'); +like(http_get("/?b=[::1]:$p6"), qr/::1:$p6/, 'ipv6 range 2'); +unlike(http_get("/?b=[::1]:$p7"), qr/::1:$p7/, 'out of range 5'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/http_method.t b/tests/nginx-tests/nginx-tests/http_method.t new file mode 100644 index 0000000000..3f8a0d7407 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/http_method.t @@ -0,0 +1,77 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for HTTP methods. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite/)->plan(2) + ->write_file_expand('nginx.conf', <<'EOF')->run(); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + return 200; + } + } +} + +EOF + +############################################################################### + +like(http(<has_version('1.21.1'); + +like(http(<new()->has(qw/http rewrite/)->plan(19) + ->write_file_expand('nginx.conf', <<'EOF')->run(); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + add_header X-URI "x $uri x"; + add_header X-Args "y $args y"; + add_header X-Request-URI "z $request_uri z"; + return 204; + } + } +} + +EOF + +############################################################################### + +like(http_get('/foo/bar%'), qr/400 Bad/, 'percent'); +like(http_get('/foo/bar%1'), qr/400 Bad/, 'percent digit'); + +like(http_get('/foo/bar/.?args'), qr!x /foo/bar/ x!, 'dot args'); +like(http_get('/foo/bar/.#frag'), qr!x /foo/bar/ x!, 'dot frag'); +like(http_get('/foo/bar/..?args'), qr!x /foo/ x!, 'dot dot args'); +like(http_get('/foo/bar/..#frag'), qr!x /foo/ x!, 'dot dot frag'); +like(http_get('/foo/bar/.'), qr!x /foo/bar/ x!, 'trailing dot'); +like(http_get('/foo/bar/..'), qr!x /foo/ x!, 'trailing dot dot'); + +like(http_get('http://localhost'), qr!x / x!, 'absolute'); +like(http_get('http://localhost/'), qr!x / x!, 'absolute slash'); +like(http_get('http://localhost?args'), qr!x / x.*y args y!ms, + 'absolute args'); +like(http_get('http://localhost?args#frag'), qr!x / x.*y args y!ms, + 'absolute args and frag'); + +like(http_get('http://localhost:8080'), qr!x / x!, 'port'); +like(http_get('http://localhost:8080/'), qr!x / x!, 'port slash'); +like(http_get('http://localhost:8080?args'), qr!x / x.*y args y!ms, + 'port args'); +like(http_get('http://localhost:8080?args#frag'), qr!x / x.*y args y!ms, + 'port args and frag'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.1'); + +like(http_get('/ /'), qr/400 Bad/, 'space'); + +} + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.1'); + +like(http_get("/\x02"), qr/400 Bad/, 'control'); + +} + +like(http_get('/%02'), qr!x /\x02 x!, 'control escaped'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/http_variables.t b/tests/nginx-tests/nginx-tests/http_variables.t index aa20e60a34..5a16a62a25 100644 --- a/tests/nginx-tests/nginx-tests/http_variables.t +++ b/tests/nginx-tests/nginx-tests/http_variables.t @@ -22,7 +22,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http rewrite proxy/)->plan(4); +my $t = Test::Nginx->new()->has(qw/http rewrite proxy/)->plan(7); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -48,6 +48,10 @@ http { return 200 OK; } + location /arg { + return 200 $arg_l:$arg_; + } + location /set { add_header Cache-Control max-age=3600; add_header Cache-Control private; @@ -59,6 +63,12 @@ http { expires epoch; proxy_pass http://127.0.0.1:8080/set; } + + location /limit_rate { + set $limit_rate $arg_l; + add_header X-Rate $limit_rate; + return 200 OK; + } } } @@ -72,21 +82,19 @@ http_get('/'); http_get('/../bad_uri'); http_get('/redefine'); -$t->stop(); +like(http_get('/arg?l=42'), qr/42:$/, 'arg'); -my $log; +# $limit_rate is a special variable that has its own set_handler / get_handler -{ - open LOG, $t->testdir() . '/cc.log' - or die("Can't open nginx access log file.\n"); - local $/; - $log = ; - close LOG; -} +like(http_get('/limit_rate?l=40k'), qr/X-Rate: 40960/, 'limit_rate handlers'); +like(http_get('/limit_rate'), qr/X-Rate: 0/, 'limit_rate invalid'); + +$t->stop(); +my $log = $t->read_file('cc.log'); like($log, qr!^: -$!m, 'no uri'); like($log, qr!^/: -$!m, 'no header'); -like($log, qr!^/set: max-age=3600[,;] private[,;] must-revalidate$!m, +like($log, qr!^/set: max-age=3600, private, must-revalidate$!m, 'multi headers'); like($log, qr!^/redefine: no-cache$!m, 'ignoring headers with (hash == 0)'); diff --git a/tests/nginx-tests/nginx-tests/ignore_invalid_headers.t b/tests/nginx-tests/nginx-tests/ignore_invalid_headers.t new file mode 100644 index 0000000000..f479be2ca4 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/ignore_invalid_headers.t @@ -0,0 +1,184 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for ignore_invalid_headers, underscores_in_headers directives. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use Socket qw/ CRLF /; +use MIME::Base64 qw/ encode_base64 decode_base64 /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(12) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + ignore_invalid_headers off; + + location / { + proxy_pass http://127.0.0.1:8085; + } + + location /v { + add_header X-Cookie $http_cookie; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location / { + proxy_pass http://127.0.0.1:8085; + } + } + + server { + listen 127.0.0.1:8082; + server_name localhost; + + underscores_in_headers on; + + location / { + proxy_pass http://127.0.0.1:8085; + } + } +} + +EOF + +$t->write_file('index.html', ''); +$t->write_file('v', ''); +$t->run_daemon(\&http_daemon); +$t->run()->waitforsocket('127.0.0.1:' . port(8085)); + +############################################################################### + +my $us = 'GET / HTTP/1.0' . CRLF + . 'x_foo: x-bar' . CRLF . CRLF; +my $us2 = 'GET / HTTP/1.0' . CRLF + . '_foo: x-bar' . CRLF . CRLF; +my $bad = 'GET / HTTP/1.0' . CRLF + . 'x.foo: x-bar' . CRLF . CRLF; +my $bad2 = 'GET / HTTP/1.0' . CRLF + . '.foo: x-bar' . CRLF . CRLF; + +# ignore_invalid_headers off; + +like(get($us, 8080), qr/x-bar/, 'off - underscore'); +like(get($us2, 8080), qr/x-bar/, 'off - underscore first'); +like(get($bad, 8080), qr/x-bar/, 'off - bad'); +like(get($bad2, 8080), qr/x-bar/, 'off - bad first'); + +# ignore_invalid_headers off; headers parsing post 8f55cb5c7e79 + +unlike(http('GET /v HTTP/1.0' . CRLF + . 'Host: localhost' . CRLF + . 'coo: foo' . CRLF + . ': x-bar' . CRLF . CRLF), qr/x-bar/, 'off - several'); + +# ignore_invalid_headers on; + +unlike(get($us, 8081), qr/x-bar/, 'on - underscore'); +unlike(get($us2, 8081), qr/x-bar/, 'on - underscore first'); + +# ignore_invalid_headers on; underscores_in_headers on; + +like(get($us, 8082), qr/x-bar/, 'underscores_in_headers'); +like(get($us2, 8082), qr/x-bar/, 'underscores_in_headers - first'); + +# always invalid header characters + +my $bad3 = 'GET / HTTP/1.0' . CRLF + . ':foo: x-bar' . CRLF . CRLF; +my $bad4 = 'GET / HTTP/1.0' . CRLF + . ' foo: x-bar' . CRLF . CRLF; +my $bad5 = 'GET / HTTP/1.0' . CRLF + . "foo\x02: x-bar" . CRLF . CRLF; + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.1'); + +like(http($bad3), qr/400 Bad/, 'colon first'); +like(http($bad4), qr/400 Bad/, 'space'); +like(http($bad5), qr/400 Bad/, 'control'); + +} + +############################################################################### + +sub get { + my ($msg, $port) = @_; + + my $s = IO::Socket::INET->new('127.0.0.1:' . port($port)) or die; + my ($headers) = http($msg, socket => $s) =~ /X-Headers: (\w+)/; + decode_base64($headers); +} + +############################################################################### + +sub http_daemon { + my $once = 1; + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => '127.0.0.1:' . port(8085), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + my $headers = ''; + my $uri = ''; + + while (<$client>) { + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + $headers = encode_base64($headers, ""); + + print $client <interlaced, 0, 'gif interlaced off'); is($im->transparent, 0, 'gif transparent white'); SKIP: { -skip 'broken libgd', 1 unless has_gdversion('2.1.0') or $ENV{TEST_NGINX_UNSAFE}; +skip 'broken/unknown libgd', 1 + unless has_gdversion('2.1.0') or $ENV{TEST_NGINX_UNSAFE}; $im = GD::Image->newFromGifData(http_get_body('/interlaced/gif')); is($im->interlaced, 1, 'gif interlaced on'); @@ -279,8 +280,9 @@ sub http_get_body { sub has_gdversion { my ($need) = @_; - my $v_str = `gdlib-config --version 2>&1` or return 1; - ($v_str) = $v_str =~ m!^([0-9.]+)! or return 1; + my $v_str = `gdlib-config --version 2>&1` + || eval { GD::VERSION_STRING() } or return 0; + ($v_str) = $v_str =~ m!^([0-9.]+)!m or return 0; my @v = split(/\./, $v_str); my ($n, $v); diff --git a/tests/nginx-tests/nginx-tests/index.t b/tests/nginx-tests/nginx-tests/index.t index c8f6dde470..584000d09b 100644 --- a/tests/nginx-tests/nginx-tests/index.t +++ b/tests/nginx-tests/nginx-tests/index.t @@ -22,7 +22,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http/)->plan(7) +my $t = Test::Nginx->new()->has(qw/http/)->plan(14) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -67,9 +67,25 @@ http { index $server_name.html; } + location /va2/ { + alias %%TESTDIR%%/; + # before 1.13.8, the token produced emerg: + # directive "index" is not terminated by ";" + index ${server_name}.html; + } + location /var_redirect/ { index /$server_name.html; } + + location /not_found/ { + error_log %%TESTDIR%%/log_not_found.log; + + location /not_found/off/ { + error_log %%TESTDIR%%/off.log; + log_not_found off; + } + } } } @@ -80,6 +96,10 @@ $t->write_file('many.html', 'manybody'); $t->write_file('re.html', 'rebody'); $t->write_file('localhost.html', 'varbody'); +my $d = $t->testdir(); +mkdir("$d/forbidden"); +chmod(0000, "$d/forbidden"); + $t->run(); ############################################################################### @@ -90,7 +110,20 @@ like(http_get('/redirect/'), qr/X-URI: \/re.html.*rebody/ms, 'redirect'); like(http_get('/loop/'), qr/500 Internal/, 'redirect loop'); like(http_get('/many/'), qr/X-URI: \/many\/many.html.*manybody/ms, 'many'); like(http_get('/var/'), qr/X-URI: \/var\/localhost.html.*varbody/ms, 'var'); +like(http_get('/va2/'), qr/X-URI: \/va2\/localhost.html.*varbody/ms, 'var 2'); like(http_get('/var_redirect/'), qr/X-URI: \/localhost.html.*varbody/ms, 'var with redirect'); +like(http_get('/not_found/'), qr/404 Not Found/, 'not found'); +like(http_get('/not_found/off/'), qr/404 Not Found/, 'not found log off'); +like(http_get('/forbidden/'), qr/403 Forbidden/, 'directory access denied'); +like(http_get('/index.html/'), qr/404 Not Found/, 'not a directory'); + +$t->stop(); + +like($t->read_file('log_not_found.log'), qr/error/, 'log_not_found'); +unlike($t->read_file('off.log'), qr/error/, 'log_not_found off'); + +chmod(0700, "$d/forbidden"); + ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js.t b/tests/nginx-tests/nginx-tests/js.t index 5c0689425d..4aad9f7b9b 100644 --- a/tests/nginx-tests/nginx-tests/js.t +++ b/tests/nginx-tests/nginx-tests/js.t @@ -1,8 +1,10 @@ #!/usr/bin/perl # (C) Roman Arutyunyan +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. -# Tests for http JavaScript module. +# Tests for http njs module. ############################################################################### @@ -10,6 +12,7 @@ use warnings; use strict; use Test::More; +use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -21,7 +24,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http rewrite njs/)->plan(13) +my $t = Test::Nginx->new()->has(qw/http rewrite/) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -34,150 +37,296 @@ events { http { %%TEST_GLOBALS_HTTP%% - js_set $test_method "'method=' + $r.method"; - js_set $test_version "'version=' + $r.httpVersion"; - js_set $test_addr "'addr=' + $r.remoteAddress"; - js_set $test_uri "'uri=' + $r.uri"; - js_set $test_hdr "'hdr=' + $r.headers.foo"; - js_set $test_ihdr "var s; - s = ''; - for (h in $r.headers) { - if (h.substr(0, 3) == 'foo') { - s += $r.headers[h]; - } - } - s;"; - js_set $test_arg "'arg=' + $r.args.foo"; - js_set $test_iarg "var s; - s = ''; - for (a in $r.args) { - if (a.substr(0, 3) == 'foo') { - s += $r.args[a]; - } - } - s;"; + js_set $test_method test.method; + js_set $test_version test.version; + js_set $test_addr test.addr; + js_set $test_uri test.uri; + js_set $test_var test.variable; + js_set $test_type test.type; + js_set $test_global test.global_obj; + js_set $test_log test.log; + js_set $test_internal test.sub_internal; + js_set $test_except test.except; + + js_import test.js; server { listen 127.0.0.1:8080; server_name localhost; - location /req_method { + location /njs { + js_content test.njs; + } + + location /method { return 200 $test_method; } - location /req_version { + location /version { return 200 $test_version; } - location /req_addr { + location /addr { return 200 $test_addr; } - location /req_uri { + location /uri { return 200 $test_uri; } - location /req_hdr { - return 200 $test_hdr; + location /var { + return 200 $test_var; + } + + location /global { + return 200 $test_global; + } + + location /body { + js_content test.request_body; + } + + location /in_file { + client_body_in_file_only on; + js_content test.request_body; + } + + location /status { + js_content test.status; } - location /req_ihdr { - return 200 $test_ihdr; + location /request_body { + js_content test.request_body; } - location /req_arg { - return 200 $test_arg; + location /request_body_cache { + js_content test.request_body_cache; } - location /req_iarg { - return 200 $test_iarg; + location /send { + js_content test.send; } - location /res_status { - js_run " - var res; - res = $r.response; - res.status = 204; - res.sendHeader(); - res.finish(); - "; + location /return_method { + js_content test.return_method; } - location /res_ctype { - js_run " - var res; - res = $r.response; - res.status = 200; - res.contentType = 'application/foo'; - res.sendHeader(); - res.finish(); - "; + location /type { + js_content test.type; } - location /res_clen { - js_run " - var res; - res = $r.response; - res.status = 200; - res.contentLength = 5; - res.sendHeader(); - res.send('foo12'); - res.finish(); - "; + location /log { + return 200 $test_log; } - location /res_send { - js_run " - var res, a, s; - res = $r.response; - res.status = 200; - res.sendHeader(); - for (a in $r.args) { - if (a.substr(0, 3) == 'foo') { - s = $r.args[a]; - res.send('n=' + a + ', v=' + s.substr(0, 2) + ' '); - } - } - res.finish(); - "; + location /internal { + js_content test.internal; } - location /res_hdr { - js_run " - var res; - res = $r.response; - res.status = 200; - res.headers['Foo'] = $r.args.fOO; - res.sendHeader(); - res.finish(); - "; + location /sub_internal { + internal; + return 200 $test_internal; + } + + location /except { + return 200 $test_except; + } + + location /content_except { + js_content test.content_except; + } + + location /content_empty { + js_content test.content_empty; } } } EOF -$t->run(); +$t->write_file('test.js', < a[v], r); + + var typ = Buffer.isBuffer(p) ? 'buffer' : (typeof p); + r.return(200, `type: \${typ}`); + } + + function log(r) { + r.log('SEE-LOG'); + } + + async function internal(r) { + let reply = await r.subrequest('/sub_internal'); + + r.return(200, `parent: \${r.internal} sub: \${reply.responseText}`); + } + + function sub_internal(r) { + return r.internal; + } + + function except(r) { + var fs = require('fs'); + fs.readFileSync(); + } + + + function content_except(r) { + JSON.parse({}.a.a); + } + + function content_empty(r) { + } + + export default {njs:test_njs, method, version, addr, uri, + variable, global_obj, status, request_body, internal, + request_body_cache, send, return_method, sub_internal, + type, log, except, content_except, content_empty}; + +EOF + +$t->try_run('no njs available')->plan(27); ############################################################################### -like(http_get('/req_method'), qr/method=GET/, 'r.method'); -like(http_get('/req_version'), qr/version=1.0/, 'r.httpVersion'); -like(http_get('/req_addr'), qr/addr=127.0.0.1/, 'r.remoteAddress'); -like(http_get('/req_uri'), qr/uri=\/req_uri/, 'r.uri'); -like(http_get_hdr('/req_hdr'), qr/hdr=12345/, 'r.headers'); -like(http_get_ihdr('/req_ihdr'), qr/12345barz/, 'r.headers iteration'); -like(http_get('/req_arg?foO=12345'), qr/arg=12345/, 'r.args'); -like(http_get('/req_iarg?foo=12345&foo2=bar&nn=22&foo-3=z'), qr/12345barz/, - 'r.args iteration'); - -like(http_get('/res_status'), qr/204 No Content/, 'r.response.status'); -like(http_get('/res_ctype'), qr/Content-Type: application\/foo/, - 'r.response.contentType'); -like(http_get('/res_clen'), qr/Content-Length: 5/, 'r.response.contentLength'); -like(http_get('/res_send?foo=12345&n=11&foo-2=bar&ndd=&foo-3=z'), - qr/n=foo, v=12 n=foo-2, v=ba n=foo-3, v=z/, 'r.response.send'); -like(http_get('/res_hdr?foo=12345'), qr/Foo: 12345/, 'r.response.headers'); +like(http_get('/method'), qr/method=GET/, 'r.method'); +like(http_get('/version'), qr/version=1.0/, 'r.httpVersion'); +like(http_get('/addr'), qr/addr=127.0.0.1/, 'r.remoteAddress'); +like(http_get('/uri'), qr/uri=\/uri/, 'r.uri'); + +like(http_get('/status'), qr/204 No Content/, 'r.status'); + +like(http_post('/body'), qr/REQ-BODY/, 'request body'); +like(http_post('/in_file'), qr/request body is in a file/, + 'request body in file'); +like(http_post_big('/body'), qr/200.*^(1234567890){1024}$/ms, + 'request body big'); + +like(http_get('/send?foo=12345&n=11&foo-2=bar&ndd=&foo-3=z'), + qr/n=foo, v=12 n=foo-2, v=ba n=foo-3, v=z/, 'r.send'); + +like(http_get('/return_method?c=200'), qr/200 OK.*\x0d\x0a?\x0d\x0a?$/s, + 'return code'); +like(http_get('/return_method?c=200&t=SEE-THIS'), qr/200 OK.*^SEE-THIS$/ms, + 'return text'); +like(http_get('/return_method?c=301&t=path'), qr/ 301 .*Location: path/s, + 'return redirect'); +like(http_get('/return_method?c=404'), qr/404 Not.*html/s, 'return error page'); +like(http_get('/return_method?c=inv'), qr/ 500 /, 'return invalid'); + +TODO: { +local $TODO = 'not yet' + unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.5.0'; + +like(http_get('/type?path=variables.host'), qr/200 OK.*type: string$/s, + 'variables type'); +like(http_get('/type?path=rawVariables.host'), qr/200 OK.*type: buffer$/s, + 'rawVariables type'); + +like(http_post('/type?path=requestText'), qr/200 OK.*type: string$/s, + 'requestText type'); +like(http_post('/type?path=requestBuffer'), qr/200 OK.*type: buffer$/s, + 'requestBuffer type'); +like(http_post('/request_body_cache'), + qr/requestText:string requestBuffer:buffer$/s, 'request body cache'); + +} + +like(http_get('/var'), qr/variable=127.0.0.1/, 'r.variables'); +like(http_get('/global'), qr/global=njs/, 'global code'); +like(http_get('/log'), qr/200 OK/, 'r.log'); + +TODO: { +local $TODO = 'not yet' + unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.7.7'; + +like(http_get('/internal'), qr/parent: false sub: true/, 'r.internal'); + +} + +http_get('/except'); +http_get('/content_except'); + +like(http_get('/content_empty'), qr/500 Internal Server Error/, + 'empty handler'); + +$t->stop(); + +ok(index($t->read_file('error.log'), 'SEE-LOG') > 0, 'log js'); +ok(index($t->read_file('error.log'), 'at fs.readFileSync') > 0, + 'js_set backtrace'); +ok(index($t->read_file('error.log'), 'at JSON.parse') > 0, + 'js_content backtrace'); ############################################################################### @@ -203,4 +352,28 @@ foo-3: z EOF } +sub http_post { + my ($url, %extra) = @_; + + my $p = "POST $url HTTP/1.0" . CRLF . + "Host: localhost" . CRLF . + "Content-Length: 8" . CRLF . + CRLF . + "REQ-BODY"; + + return http($p, %extra); +} + +sub http_post_big { + my ($url, %extra) = @_; + + my $p = "POST $url HTTP/1.0" . CRLF . + "Host: localhost" . CRLF . + "Content-Length: 10240" . CRLF . + CRLF . + ("1234567890" x 1024); + + return http($p, %extra); +} + ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_args.t b/tests/nginx-tests/nginx-tests/js_args.t new file mode 100644 index 0000000000..a9a47ff841 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_args.t @@ -0,0 +1,151 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, arguments tests. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { require JSON::PP; }; +plan(skip_all => "JSON::PP not installed") if $@; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + js_set $test_iter test.iter; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /iter { + return 200 $test_iter; + } + + location /keys { + js_content test.keys; + } + + location /object { + js_content test.object; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs')->plan(15); + +############################################################################### + +sub recode { + my $json; + eval { $json = JSON::PP::decode_json(shift) }; + + if ($@) { + return ""; + } + + JSON::PP->new()->canonical()->encode($json); +} + +sub get_json { + http_get(shift) =~ /\x0d\x0a?\x0d\x0a?(.*)/ms; + recode($1); +} + +############################################################################### + +TODO: { +local $TODO = 'not yet' + unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.7.6'; + +like(http_get('/iter?foo=12345&foo2=bar&nn=22&foo-3=z'), qr/12345barz/, + 'r.args iteration'); +like(http_get('/iter?foo=123&foo2=&foo3&foo4=456'), qr/123456/, + 'r.args iteration 2'); +like(http_get('/iter?foo=123&foo2=&foo3'), qr/123/, 'r.args iteration 3'); +like(http_get('/iter?foo=123&foo2='), qr/123/, 'r.args iteration 4'); +like(http_get('/iter?foo=1&foo=2'), qr/1,2/m, 'r.args iteration 5'); + +like(http_get('/keys?b=1&c=2&a=5'), qr/a,b,c/m, 'r.args sorted keys'); +like(http_get('/keys?b=1&b=2'), qr/b/m, 'r.args duplicate keys'); +like(http_get('/keys?b=1&a&c='), qr/a,b,c/m, 'r.args empty value'); + +is(get_json('/object'), '{}', 'empty object'); +is(get_json('/object?a=1&b=2&c=3'), '{"a":"1","b":"2","c":"3"}', + 'ordinary object'); +is(get_json('/object?a=1&A=2'), '{"A":"2","a":"1"}', + 'case sensitive object'); +is(get_json('/object?a=1&A=2&a=3'), '{"A":"2","a":["1","3"]}', + 'duplicate keys object'); +is(get_json('/object?%61=1&a=2'), '{"a":["1","2"]}', + 'keys percent-encoded object'); +is(get_json('/object?a=%62%63&b=%63%64'), '{"a":"bc","b":"cd"}', + 'values percent-encoded object'); +is(get_json('/object?a=%6&b=%&c=%%&d=%zz'), + '{"a":"%6","b":"%","c":"%%","d":"%zz"}', + 'values percent-encoded broken object'); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_async.t b/tests/nginx-tests/nginx-tests/js_async.t new file mode 100644 index 0000000000..6c75a7b95c --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_async.t @@ -0,0 +1,231 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Async tests for http njs module. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_set $test_async test.set_timeout; + js_set $context_var test.context_var; + js_set $test_set_rv_var test.set_rv_var; + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /async_var { + return 200 $test_async; + } + + location /shared_ctx { + add_header H $context_var; + js_content test.shared_ctx; + } + + location /set_timeout { + js_content test.set_timeout; + } + + location /set_timeout_many { + js_content test.set_timeout_many; + } + + location /set_timeout_data { + postpone_output 0; + js_content test.set_timeout_data; + } + + location /limit_rate { + postpone_output 0; + sendfile_max_chunk 5; + js_content test.limit_rate; + } + + location /async_content { + js_content test.async_content; + } + + location /set_rv_var { + return 200 $test_set_rv_var; + } + } +} + +EOF + +$t->write_file('test.js', < {resolve(x)}).then(v => v).then(v => v); + } + + async function async_content(r) { + const a1 = await pr('A'); + const a2 = await pr('B'); + + r.return(200, `retval: \${a1 + a2}`); + } + + async function set_rv_var(r) { + const a1 = await pr(10); + const a2 = await pr(20); + + r.setReturnValue(`retval: \${a1 + a2}`); + } + + export default {njs:test_njs, set_timeout, set_timeout_data, + set_timeout_many, context_var, shared_ctx, limit_rate, + async_content, set_rv_var}; + +EOF + +$t->try_run('no njs available')->plan(9); + +############################################################################### + +like(http_get('/set_timeout'), qr/Content-Type: foo/, 'setTimeout'); +like(http_get('/set_timeout_many'), qr/Content-Type: reply/, 'setTimeout many'); +like(http_get('/set_timeout_data'), qr/123456789/, 'setTimeout data'); +like(http_get('/shared_ctx?a=xxx'), qr/H: xxx/, 'shared context'); +like(http_get('/limit_rate'), qr/A{50}/, 'limit_rate'); + +TODO: { +local $TODO = 'not yet' + unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.7.0'; + +like(http_get('/async_content'), qr/retval: AB/, 'async content'); +like(http_get('/set_rv_var'), qr/retval: 30/, 'set return value variable'); + +} + +http_get('/async_var'); + +$t->stop(); + +ok(index($t->read_file('error.log'), 'pending events') > 0, + 'pending js events'); +ok(index($t->read_file('error.log'), 'async operation inside') > 0, + 'async op in var handler'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_body_filter.t b/tests/nginx-tests/nginx-tests/js_body_filter.t new file mode 100644 index 0000000000..b90a25027e --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_body_filter.t @@ -0,0 +1,168 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, body filter. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /append { + js_body_filter test.append; + proxy_pass http://127.0.0.1:8081/source; + } + + location /buffer_type { + js_body_filter test.buffer_type buffer_type=buffer; + proxy_pass http://127.0.0.1:8081/source; + } + + location /forward { + js_body_filter test.forward buffer_type=string; + proxy_pass http://127.0.0.1:8081/source; + } + + location /filter { + proxy_buffering off; + js_body_filter test.filter; + proxy_pass http://127.0.0.1:8081/source; + } + + location /prepend { + js_body_filter test.prepend; + proxy_pass http://127.0.0.1:8081/source; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location /source { + postpone_output 1; + js_content test.source; + } + } +} + +EOF + +$t->write_file('test.js', <= Number(r.args.len)) { + r.sendBuffer(`\${data}|`, flags); + + if (r.args.dup && !flags.last) { + r.sendBuffer(data, flags); + } + } + } + + function forward(r, data, flags) { + r.sendBuffer(data, flags); + } + + function prepend(r, data, flags) { + r.sendBuffer("XXX"); + r.sendBuffer(data, flags); + r.done(); + } + + export default {njs: test_njs, append, buffer_type, filter, forward, + prepend, source}; + +EOF + +$t->try_run('no njs body filter')->plan(6); + +############################################################################### + +like(http_get('/append'), qr/AAABBCDDDDXXX/, 'append'); +like(http_get('/buffer_type'), qr/AAABBCDDDD/, 'buffer type'); +like(http_get('/forward'), qr/AAABBCDDDD/, 'forward'); +like(http_get('/filter?len=3'), qr/AAA|DDDD|/, 'filter 3'); +like(http_get('/filter?len=2&dup=1'), qr/AAA|AAABB|BBDDDD|DDDD/, + 'filter 2 dup'); +like(http_get('/prepend'), qr/XXXAAABBCDDDD/, 'prepend'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_body_filter_if.t b/tests/nginx-tests/nginx-tests/js_body_filter_if.t new file mode 100644 index 0000000000..af0aa865d9 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_body_filter_if.t @@ -0,0 +1,127 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, body filter, if context. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /filter { + if ($arg_name ~ "prepend") { + js_body_filter test.prepend; + } + + if ($arg_name ~ "append") { + js_body_filter test.append; + } + + js_body_filter test.should_not_be_called; + + proxy_pass http://127.0.0.1:8081/source; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location /source { + postpone_output 1; + js_content test.source; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs body filter')->plan(2); + +############################################################################### + +like(http_get('/filter?name=append'), qr/AAABBCDDDDXXX/, 'append'); +like(http_get('/filter?name=prepend'), qr/XXXAAABBCDDDD/, 'prepend'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_buffer.t b/tests/nginx-tests/nginx-tests/js_buffer.t new file mode 100644 index 0000000000..ce4c15d020 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_buffer.t @@ -0,0 +1,184 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, buffer properties. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { require JSON::PP; }; +plan(skip_all => "JSON::PP not installed") if $@; + +my $t = Test::Nginx->new()->has(qw/http rewrite proxy/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /return { + js_content test.return; + } + + location /req_body { + js_content test.req_body; + } + + location /res_body { + js_content test.res_body; + } + + location /res_text { + js_content test.res_text; + } + + location /binary_var { + js_content test.binary_var; + } + + location /p/ { + proxy_pass http://127.0.0.1:8081/; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location /sub1 { + return 200 '{"a": {"b": 1}}'; + } + } +} + +EOF + +$t->write_file('test.js', < { + var body = reply.responseBuffer; + var view = new DataView(body.buffer); + view.setInt8(2, 'c'.charCodeAt(0)); + body = JSON.parse(body); + body.type = type(reply.responseBuffer); + r.return(200, JSON.stringify(body)); + }) + } + + function res_text(r) { + r.subrequest('/p/sub1') + .then(reply => { + var body = JSON.parse(reply.responseText); + body.type = type(reply.responseText); + r.return(200, JSON.stringify(body)); + }) + } + + function binary_var(r) { + var test = r.rawVariables.binary_remote_addr + .equals(Buffer.from([127,0,0,1])); + r.return(200, test); + } + + export default {njs: test_njs, return: test_return, req_body, res_body, + res_text, binary_var}; + +EOF + +$t->try_run('no njs buffer')->plan(5); + +############################################################################### + +like(http_get('/return?text=FOO'), qr/200 OK.*body: FOO$/s, + 'return buffer'); +like(http_post('/req_body'), qr/200 OK.*BAR$/s, 'request buffer'); +is(get_json('/res_body'), '{"c":{"b":1},"type":"buffer"}', 'response buffer'); +is(get_json('/res_text'), '{"a":{"b":1},"type":"string"}', 'response text'); +like(http_get('/binary_var'), qr/200 OK.*true$/s, + 'binary var'); + +############################################################################### + +sub recode { + my $json; + eval { $json = JSON::PP::decode_json(shift) }; + + if ($@) { + return ""; + } + + JSON::PP->new()->canonical()->encode($json); +} + +sub get_json { + http_get(shift) =~ /\x0d\x0a?\x0d\x0a?(.*)/ms; + recode($1); +} + +sub http_post { + my ($url, %extra) = @_; + + my $p = "POST $url HTTP/1.0" . CRLF . + "Host: localhost" . CRLF . + "Content-Length: 17" . CRLF . + CRLF . + "{\"a\":{\"b\":\"BAR\"}}"; + + return http($p, %extra); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_dump.t b/tests/nginx-tests/nginx-tests/js_dump.t new file mode 100644 index 0000000000..c00a53a2e8 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_dump.t @@ -0,0 +1,110 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, request object dump. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /dump { + js_content test.dump; + } + + location /stringify { + js_content test.stringify; + } + + location /stringify_subrequest { + js_content test.stringify_subrequest; + } + + location /js_sub { + return 201 '{$request_method}'; + } + } +} + +EOF + +$t->write_file('test.js', < { + r.return(200, JSON.stringify(reply)) + }); + } + + export default {dump, stringify, stringify_subrequest}; + +EOF + +$t->try_run('no njs dump')->plan(3); + +############################################################################### + +like(http( + 'GET /dump?v=1&t=x HTTP/1.0' . CRLF + . 'Foo: bar' . CRLF + . 'Foo2: bar2' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/method:'GET'/, 'njs.dump(r)'); + +like(http( + 'GET /stringify?v=1&t=x HTTP/1.0' . CRLF + . 'Foo: bar' . CRLF + . 'Foo2: bar2' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/headersOut":\{"baz":"bar"}/, 'JSON.stringify(r)'); + +like(http( + 'GET /stringify_subrequest HTTP/1.0' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/"status":201/, 'JSON.stringify(reply)'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_fetch.t b/tests/nginx-tests/nginx-tests/js_fetch.t new file mode 100644 index 0000000000..eae83d2347 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_fetch.t @@ -0,0 +1,656 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, fetch method. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { require JSON::PP; }; +plan(skip_all => "JSON::PP not installed") if $@; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /broken { + js_content test.broken; + } + + location /broken_response { + js_content test.broken_response; + } + + location /body { + js_content test.body; + } + + location /body_special { + js_content test.body_special; + } + + location /chain { + js_content test.chain; + } + + location /chunked { + js_content test.chunked; + } + + location /header { + js_content test.header; + } + + location /header_iter { + js_content test.header_iter; + } + + location /multi { + js_content test.multi; + } + + location /property { + js_content test.property; + } + } + + server { + listen 127.0.0.1:8080; + server_name aaa; + + location /loc { + js_content test.loc; + } + + location /json { } + } + + server { + listen 127.0.0.1:8080; + server_name bbb; + + location /loc { + js_content test.loc; + } + } + + server { + listen 127.0.0.1:8081; + server_name ccc; + + location /loc { + js_content test.loc; + } + } +} + +EOF + +my $p0 = port(8080); +my $p1 = port(8081); +my $p2 = port(8082); + +$t->write_file('json', '{"a":[1,2], "b":{"c":"FIELD"}}'); + +$t->write_file('test.js', < a[v], obj); + } + + return JSON.stringify(retval); + } + + ngx.fetch(`http://127.0.0.1:$p0/\${loc}`, {headers: {Host: 'aaa'}}) + .then(reply => reply[getter]()) + .then(data => r.return(200, query(data))) + .catch(e => r.return(501, e.message)) + } + + function property(r) { + var opts = {headers:{Host: 'aaa'}}; + + if (r.args.code) { + opts.headers.code = r.args.code; + } + + var p = ngx.fetch('http://127.0.0.1:$p0/loc', opts) + + if (r.args.readBody) { + p = p.then(rep => + rep.text().then(body => {rep.text = body; return rep;})) + } + + p.then(reply => r.return(200, reply[r.args.pr])) + .catch(e => r.return(501, e.message)) + } + + function process_errors(r, tests) { + var results = []; + + tests.forEach(args => { + ngx.fetch.apply(r, args) + .then(reply => { + r.return(400, '["unexpected then"]'); + }) + .catch(e => { + results.push(e.message); + + if (results.length == tests.length) { + results.sort(); + r.return(200, JSON.stringify(results)); + } + }) + }) + } + + function broken(r) { + var tests = [ + ['http://127.0.0.1:1/loc'], + ['http://127.0.0.1:80800/loc'], + [Symbol.toStringTag], + ]; + + return process_errors(r, tests); + } + + function broken_response(r) { + var tests = [ + ['http://127.0.0.1:$p2/status_line'], + ['http://127.0.0.1:$p2/length'], + ['http://127.0.0.1:$p2/header'], + ['http://127.0.0.1:$p2/headers'], + ['http://127.0.0.1:$p2/content_length'], + ]; + + return process_errors(r, tests); + } + + function chain(r) { + var results = []; + var reqs = [ + ['http://127.0.0.1:$p0/loc', {headers: {Host:'aaa'}}], + ['http://127.0.0.1:$p0/loc', {headers: {Host:'bbb'}}], + ]; + + function next(reply) { + if (reqs.length == 0) { + r.return(200, "SUCCESS"); + return; + } + + ngx.fetch.apply(r, reqs.pop()) + .then(next) + .catch(e => r.return(400, e.message)) + } + + next(); + } + + function chunked(r) { + var results = []; + var tests = [ + ['http://127.0.0.1:$p2/big', {max_response_body_size:128000}], + ['http://127.0.0.1:$p2/big/ok', {max_response_body_size:128000}], + ['http://127.0.0.1:$p2/chunked'], + ['http://127.0.0.1:$p2/chunked/ok'], + ['http://127.0.0.1:$p2/chunked/big', {max_response_body_size:128}], + ['http://127.0.0.1:$p2/chunked/big'], + ]; + + function collect(v) { + results.push(v); + + if (results.length == tests.length) { + results.sort(); + r.return(200, JSON.stringify(results)); + } + } + + tests.forEach(args => { + ngx.fetch.apply(r, args) + .then(reply => reply.text()) + .then(body => collect(body.length)) + .catch(e => collect(e.message)) + }) + } + + function header(r) { + var url = `http://127.0.0.1:$p2/\${r.args.loc}`; + var method = r.args.method ? r.args.method : 'get'; + + var p = ngx.fetch(url) + + if (r.args.readBody) { + p = p.then(rep => + rep.text().then(body => {rep.text = body; return rep;})) + } + + p.then(reply => { + var h = reply.headers[method](r.args.h); + r.return(200, njs.dump(h)); + }) + .catch(e => r.return(501, e.message)) + } + + async function body_special(r) { + let reply = await ngx.fetch(`http://127.0.0.1:$p2/\${r.args.loc}`); + let body = await reply.text(); + + r.return(200, body); + } + + async function header_iter(r) { + let url = `http://127.0.0.1:$p2/\${r.args.loc}`; + + let response = await ngx.fetch(url); + + let headers = response.headers; + let out = []; + for (let key in response.headers) { + if (key != 'Connection') { + out.push(`\${key}:\${headers.get(key)}`); + } + } + + r.return(200, njs.dump(out)); + } + + function multi(r) { + var results = []; + var tests = [ + [ + 'http://127.0.0.1:$p0/loc', + { headers: {Code: 201, Host: 'aaa'}}, + ], + [ + 'http://127.0.0.1:$p0/loc', + { method:'POST', headers: {Code: 401, Host: 'bbb'}, body: 'OK'}, + ], + [ + 'http://127.0.0.1:$p1/loc', + { method:'PATCH', + headers: {foo:undefined, bar:'xxx', Host: 'ccc'}}, + ], + ]; + + function cmp(a,b) { + if (a.b > b.b) {return 1;} + if (a.b < b.b) {return -1;} + return 0 + } + + tests.forEach(args => { + ngx.fetch.apply(r, args) + .then(rep => + rep.text().then(body => {rep.text = body; return rep;})) + .then(rep => { + results.push({b:rep.text, + c:rep.status, + u:rep.url}); + + if (results.length == tests.length) { + results.sort(cmp); + r.return(200, JSON.stringify(results)); + } + }) + .catch(e => { + r.return(400, `["\${e.message}"]`); + throw e; + }) + }) + + if (r.args.throw) { + throw 'Oops'; + } + } + + function str(v) { return v ? v : ''}; + + function loc(r) { + var v = r.variables; + var body = str(r.requestText); + var foo = str(r.headersIn.foo); + var bar = str(r.headersIn.bar); + var c = r.headersIn.code ? Number(r.headersIn.code) : 200; + r.return(c, `\${v.host}:\${v.request_method}:\${foo}:\${bar}:\${body}`); + } + + export default {njs: test_njs, body, broken, broken_response, body_special, + chain, chunked, header, header_iter, multi, loc, property}; +EOF + +$t->try_run('no njs.fetch')->plan(31); + +$t->run_daemon(\&http_daemon, port(8082)); +$t->waitforsocket('127.0.0.1:' . port(8082)); + +############################################################################### + +like(http_get('/body?getter=arrayBuffer&loc=loc'), qr/200 OK.*"aaa:GET:::"$/s, + 'fetch body arrayBuffer'); +like(http_get('/body?getter=text&loc=loc'), qr/200 OK.*"aaa:GET:::"$/s, + 'fetch body text'); +like(http_get('/body?getter=json&loc=json&path=b.c'), + qr/200 OK.*"FIELD"$/s, 'fetch body json'); +like(http_get('/body?getter=json&loc=loc'), qr/501/s, + 'fetch body json invalid'); +like(http_get('/body_special?loc=parted'), qr/200 OK.*X{32000}$/s, + 'fetch body parted'); +like(http_get('/property?pr=bodyUsed'), qr/false$/s, + 'fetch bodyUsed false'); +like(http_get('/property?pr=bodyUsed&readBody=1'), qr/true$/s, + 'fetch bodyUsed true'); +like(http_get('/property?pr=ok'), qr/200 OK.*true$/s, + 'fetch ok true'); +like(http_get('/property?pr=ok&code=401'), qr/200 OK.*false$/s, + 'fetch ok false'); +like(http_get('/property?pr=redirected'), qr/200 OK.*false$/s, + 'fetch redirected false'); +like(http_get('/property?pr=statusText'), qr/200 OK.*OK$/s, + 'fetch statusText OK'); +like(http_get('/property?pr=statusText&code=403'), qr/200 OK.*Forbidden$/s, + 'fetch statusText Forbidden'); +like(http_get('/property?pr=type'), qr/200 OK.*basic$/s, + 'fetch type'); +like(http_get('/header?loc=duplicate_header&h=BAR'), qr/200 OK.*c$/s, + 'fetch header'); +like(http_get('/header?loc=duplicate_header&h=BARR'), qr/200 OK.*null$/s, + 'fetch no header'); +like(http_get('/header?loc=duplicate_header&h=foo'), qr/200 OK.*a,b$/s, + 'fetch header duplicate'); +like(http_get('/header?loc=duplicate_header&h=BAR&method=getAll'), + qr/200 OK.*\['c']$/s, 'fetch getAll header'); +like(http_get('/header?loc=duplicate_header&h=BARR&method=getAll'), + qr/200 OK.*\[]$/s, 'fetch getAll no header'); +like(http_get('/header?loc=duplicate_header&h=FOO&method=getAll'), + qr/200 OK.*\['a','b']$/s, 'fetch getAll duplicate'); +like(http_get('/header?loc=duplicate_header&h=bar&method=has'), + qr/200 OK.*true$/s, 'fetch header has'); +like(http_get('/header?loc=duplicate_header&h=buz&method=has'), + qr/200 OK.*false$/s, 'fetch header does not have'); +like(http_get('/header?loc=chunked/big&h=BAR&readBody=1'), qr/200 OK.*xxx$/s, + 'fetch chunked header'); +is(get_json('/multi'), + '[{"b":"aaa:GET:::","c":201,"u":"http://127.0.0.1:'.$p0.'/loc"},' . + '{"b":"bbb:POST:::OK","c":401,"u":"http://127.0.0.1:'.$p0.'/loc"},' . + '{"b":"ccc:PATCH::xxx:","c":200,"u":"http://127.0.0.1:'.$p1.'/loc"}]', + 'fetch multi'); +like(http_get('/multi?throw=1'), qr/500/s, 'fetch destructor'); +is(get_json('/broken'), + '[' . + '"connect failed",' . + '"failed to convert url arg",' . + '"invalid url"]', 'fetch broken'); +is(get_json('/broken_response'), + '["invalid fetch content length",' . + '"invalid fetch header",' . + '"invalid fetch status line",' . + '"prematurely closed connection",' . + '"prematurely closed connection"]', 'fetch broken response'); +is(get_json('/chunked'), + '[10,100010,25500,' . + '"invalid fetch chunked response",' . + '"prematurely closed connection",' . + '"very large fetch chunked response"]', 'fetch chunked'); +like(http_get('/chain'), qr/200 OK.*SUCCESS$/s, 'fetch chain'); + +TODO: { +todo_skip 'leaves coredump', 1 unless $ENV{TEST_NGINX_UNSAFE} + or http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.7.4'; + +like(http_get('/header_iter?loc=duplicate_header_large'), + qr/\['A:a','B:a','C:a','D:a','E:a','F:a','G:a','H:a','Foo:a,b']$/s, + 'fetch header duplicate large'); + +} + +TODO: { +local $TODO = 'not yet' + unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.7.7'; + +like(http_get('/body_special?loc=no_content_length'), + qr/200 OK.*CONTENT-BODY$/s, 'fetch body without content-length'); +like(http_get('/body_special?loc=no_content_length/parted'), + qr/200 OK.*X{32000}$/s, 'fetch body without content-length parted'); + +} + +############################################################################### + +sub recode { + my $json; + eval { $json = JSON::PP::decode_json(shift) }; + + if ($@) { + return ""; + } + + JSON::PP->new()->canonical()->encode($json); +} + +sub get_json { + http_get(shift) =~ /\x0d\x0a?\x0d\x0a?(.*)/ms; + recode($1); +} + +############################################################################### + +sub http_daemon { + my $port = shift; + + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . $port, + Listen => 5, + Reuse => 1 + ) or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + my $headers = ''; + my $uri = ''; + + while (<$client>) { + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; + + if ($uri eq '/status_line') { + print $client + "HTTP/1.1 2A"; + + } elsif ($uri eq '/content_length') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: " . CRLF . + "Connection: close" . CRLF . + CRLF; + + } elsif ($uri eq '/header') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "@#" . CRLF . + "Connection: close" . CRLF . + CRLF; + + } elsif ($uri eq '/duplicate_header') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Foo: a" . CRLF . + "bar: c" . CRLF . + "Foo: b" . CRLF . + "Connection: close" . CRLF . + CRLF; + + } elsif ($uri eq '/duplicate_header_large') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "A: a" . CRLF . + "B: a" . CRLF . + "C: a" . CRLF . + "D: a" . CRLF . + "E: a" . CRLF . + "F: a" . CRLF . + "G: a" . CRLF . + "H: a" . CRLF . + "Foo: a" . CRLF . + "Foo: b" . CRLF . + "Connection: close" . CRLF . + CRLF; + + } elsif ($uri eq '/headers') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Connection: close" . CRLF; + + } elsif ($uri eq '/length') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: 100" . CRLF . + "Connection: close" . CRLF . + CRLF . + "unfinished" . CRLF; + + } elsif ($uri eq '/parted') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: 32000" . CRLF . + "Connection: close" . CRLF . + CRLF; + + for (1 .. 4) { + select undef, undef, undef, 0.01; + print $client "X" x 8000; + } + + } elsif ($uri eq '/no_content_length') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Connection: close" . CRLF . + CRLF . + "CONTENT-BODY"; + + } elsif ($uri eq '/no_content_length/parted') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Connection: close" . CRLF . + CRLF; + + for (1 .. 4) { + select undef, undef, undef, 0.01; + print $client "X" x 8000; + } + + } elsif ($uri eq '/big') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: 100100" . CRLF . + "Connection: close" . CRLF . + CRLF; + for (1 .. 1000) { + print $client ("X" x 98) . CRLF; + } + print $client "unfinished" . CRLF; + + } elsif ($uri eq '/big/ok') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: 100010" . CRLF . + "Connection: close" . CRLF . + CRLF; + for (1 .. 1000) { + print $client ("X" x 98) . CRLF; + } + print $client "finished" . CRLF; + + } elsif ($uri eq '/chunked') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Transfer-Encoding: chunked" . CRLF . + "Connection: close" . CRLF . + CRLF . + "ff" . CRLF . + "unfinished" . CRLF; + + } elsif ($uri eq '/chunked/ok') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Transfer-Encoding: chunked" . CRLF . + "Connection: close" . CRLF . + CRLF . + "a" . CRLF . + "finished" . CRLF . + CRLF . "0" . CRLF . CRLF; + } elsif ($uri eq '/chunked/big') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Transfer-Encoding: chunked" . CRLF . + "Bar: xxx" . CRLF . + "Connection: close" . CRLF . + CRLF; + + for (1 .. 100) { + print $client "ff" . CRLF . ("X" x 255) . CRLF; + } + + print $client "0" . CRLF . CRLF; + } + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_fetch_https.t b/tests/nginx-tests/nginx-tests/js_fetch_https.t new file mode 100644 index 0000000000..34d9dbde7e --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_fetch_https.t @@ -0,0 +1,286 @@ +#!/usr/bin/perl + +# (C) Antoine Bonavita +# (C) Nginx, Inc. + +# Tests for http njs module, fetch method, https support. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_ssl rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + resolver 127.0.0.1:%%PORT_8981_UDP%%; + resolver_timeout 1s; + + location /njs { + js_content test.njs; + } + + location /https { + js_content test.https; + } + + location /https.myca { + js_content test.https; + + js_fetch_ciphers HIGH:!aNull:!MD5; + js_fetch_protocols TLSv1.1 TLSv1.2; + js_fetch_trusted_certificate myca.crt; + } + + location /https.myca.short { + js_content test.https; + + js_fetch_verify_depth 0; + js_fetch_trusted_certificate myca.crt; + } + } + + server { + listen 127.0.0.1:8081 ssl default; + server_name default.example.com; + + ssl_certificate default.example.com.chained.crt; + ssl_certificate_key default.example.com.key; + + location /loc { + return 200 "You are at default.example.com."; + } + } + + server { + listen 127.0.0.1:8081 ssl; + server_name 1.example.com; + + ssl_certificate 1.example.com.chained.crt; + ssl_certificate_key 1.example.com.key; + + location /loc { + return 200 "You are at 1.example.com."; + } + } +} + +EOF + +my $p1 = port(8081); + +$t->write_file('test.js', < reply.text()) + .then(body => r.return(200, body)) + .catch(e => r.return(501, e.message)) + } + + export default {njs: test_njs, https}; +EOF + +my $d = $t->testdir(); + +$t->write_file('openssl.conf', <write_file('myca.conf', <>$d/openssl.out 2>&1") == 0 + or die "Can't create self-signed certificate for CA: $!\n"; + +foreach my $name ('intermediate', 'default.example.com', '1.example.com') { + system("openssl req -new " + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.csr -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate signing req for $name: $!\n"; +} + +$t->write_file('certserial', '1000'); +$t->write_file('certindex', ''); + +system("openssl ca -batch -config $d/myca.conf " + . "-keyfile $d/myca.key -cert $d/myca.crt " + . "-subj /CN=intermediate/ -in $d/intermediate.csr " + . "-out $d/intermediate.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for intermediate: $!\n"; + +foreach my $name ('default.example.com', '1.example.com') { + system("openssl ca -batch -config $d/myca.conf " + . "-keyfile $d/intermediate.key -cert $d/intermediate.crt " + . "-subj /CN=$name/ -in $d/$name.csr -out $d/$name.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for $name $!\n"; + $t->write_file("$name.chained.crt", $t->read_file("$name.crt") + . $t->read_file('intermediate.crt')); +} + +$t->try_run('no njs.fetch')->plan(7); + +$t->run_daemon(\&dns_daemon, port(8981), $t); +$t->waitforfile($t->testdir . '/' . port(8981)); + +############################################################################### + +local $TODO = 'not yet' + unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.7.0'; + +like(http_get('/https?domain=default.example.com&verify=false'), + qr/You are at default.example.com.$/s, 'fetch https'); +like(http_get('/https?domain=127.0.0.1&verify=false'), + qr/You are at default.example.com.$/s, 'fetch https by IP'); +like(http_get('/https?domain=1.example.com&verify=false'), + qr/You are at 1.example.com.$/s, 'fetch tls extension'); +like(http_get('/https.myca?domain=default.example.com'), + qr/You are at default.example.com.$/s, 'fetch https trusted certificate'); +like(http_get('/https.myca?domain=localhost'), + qr/connect failed/s, 'fetch https wrong CN certificate'); +like(http_get('/https?domain=default.example.com'), + qr/connect failed/s, 'fetch https non trusted CA'); +like(http_get('/https.myca.short?domain=default.example.com'), + qr/connect failed/s, 'fetch https CA too far'); + +############################################################################### + +sub reply_handler { + my ($recv_data, $port, %extra) = @_; + + my (@name, @rdata); + + use constant NOERROR => 0; + use constant A => 1; + use constant IN => 1; + + # default values + + my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 3600); + + # decode name + + my ($len, $offset) = (undef, 12); + while (1) { + $len = unpack("\@$offset C", $recv_data); + last if $len == 0; + $offset++; + push @name, unpack("\@$offset A$len", $recv_data); + $offset += $len; + } + + $offset -= 1; + my ($id, $type, $class) = unpack("n x$offset n2", $recv_data); + + my $name = join('.', @name); + + if ($type == A) { + push @rdata, rd_addr($ttl, '127.0.0.1'); + } + + $len = @name; + pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata, + 0, 0, @name, $type, $class) . join('', @rdata); +} + +sub rd_addr { + my ($ttl, $addr) = @_; + + my $code = 'split(/\./, $addr)'; + + return pack 'n3N', 0xc00c, A, IN, $ttl if $addr eq ''; + + pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code); +} + +sub dns_daemon { + my ($port, $t) = @_; + + my ($data, $recv_data); + my $socket = IO::Socket::INET->new( + LocalAddr => '127.0.0.1', + LocalPort => $port, + Proto => 'udp', + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + # signal we are ready + + open my $fh, '>', $t->testdir() . '/' . $port; + close $fh; + + while (1) { + $socket->recv($recv_data, 65536); + $data = reply_handler($recv_data, $port); + $socket->send($data); + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_fetch_resolver.t b/tests/nginx-tests/nginx-tests/js_fetch_resolver.t new file mode 100644 index 0000000000..8b0dc450d0 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_fetch_resolver.t @@ -0,0 +1,231 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, fetch method, dns support. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use IO::Select; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +plan(skip_all => '127.0.0.2 local address required') + unless defined IO::Socket::INET->new( LocalAddr => '127.0.0.2' ); + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /dns { + js_content test.dns; + + resolver 127.0.0.1:%%PORT_8981_UDP%%; + resolver_timeout 1s; + } + } + + server { + listen 127.0.0.1:8080; + server_name aaa; + + location /loc { + js_content test.loc; + } + } + + server { + listen 127.0.0.1:8080; + server_name many; + + location /loc { + js_content test.loc; + } + } +} + +EOF + +my $p0 = port(8080); + +$t->write_file('test.js', < reply.text()) + .then(body => r.return(200, body)) + .catch(e => r.return(501, e.message)) + } + + function str(v) { return v ? v : ''}; + + function loc(r) { + var v = r.variables; + var body = str(r.requestText); + var foo = str(r.headersIn.foo); + var bar = str(r.headersIn.bar); + var c = r.headersIn.code ? Number(r.headersIn.code) : 200; + r.return(c, `\${v.host}:\${v.request_method}:\${foo}:\${bar}:\${body}`); + } + + export default {njs: test_njs, dns, loc}; +EOF + +$t->try_run('no njs.fetch')->plan(3); + +$t->run_daemon(\&dns_daemon, port(8981), $t); +$t->waitforfile($t->testdir . '/' . port(8981)); + +############################################################################### + +like(http_get('/dns?domain=aaa'), qr/aaa:GET:::$/s, 'fetch dns aaa'); +like(http_get('/dns?domain=many'), qr/many:GET:::$/s, 'fetch dns many'); +like(http_get('/dns?domain=unknown'), qr/"unknown" could not be resolved/s, + 'fetch dns unknown'); + +############################################################################### + +sub reply_handler { + my ($recv_data, $port, %extra) = @_; + + my (@name, @rdata); + + use constant NOERROR => 0; + use constant FORMERR => 1; + use constant SERVFAIL => 2; + use constant NXDOMAIN => 3; + + use constant A => 1; + + use constant IN => 1; + + # default values + + my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 3600); + + # decode name + + my ($len, $offset) = (undef, 12); + while (1) { + $len = unpack("\@$offset C", $recv_data); + last if $len == 0; + $offset++; + push @name, unpack("\@$offset A$len", $recv_data); + $offset += $len; + } + + $offset -= 1; + my ($id, $type, $class) = unpack("n x$offset n2", $recv_data); + + my $name = join('.', @name); + + if ($name eq 'aaa' && $type == A) { + push @rdata, rd_addr($ttl, '127.0.0.1'); + + } elsif ($name eq 'many' && $type == A) { + push @rdata, rd_addr($ttl, '127.0.0.2'); + push @rdata, rd_addr($ttl, '127.0.0.1'); + } + + $len = @name; + pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata, + 0, 0, @name, $type, $class) . join('', @rdata); +} + +sub rd_addr { + my ($ttl, $addr) = @_; + + my $code = 'split(/\./, $addr)'; + + return pack 'n3N', 0xc00c, A, IN, $ttl if $addr eq ''; + + pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code); +} + +sub dns_daemon { + my ($port, $t, %extra) = @_; + + my ($data, $recv_data); + my $socket = IO::Socket::INET->new( + LocalAddr => '127.0.0.1', + LocalPort => $port, + Proto => 'udp', + ) + or die "Can't create listening socket: $!\n"; + + my $sel = IO::Select->new($socket); + + local $SIG{PIPE} = 'IGNORE'; + + # signal we are ready + + open my $fh, '>', $t->testdir() . '/' . $port; + close $fh; + + while (my @ready = $sel->can_read) { + foreach my $fh (@ready) { + if ($socket == $fh) { + $fh->recv($recv_data, 65536); + $data = reply_handler($recv_data, $port); + $fh->send($data); + + } else { + $fh->recv($recv_data, 65536); + unless (length $recv_data) { + $sel->remove($fh); + $fh->close; + next; + } + +again: + my $len = unpack("n", $recv_data); + $data = substr $recv_data, 2, $len; + $data = reply_handler($data, $port, tcp => 1); + $data = pack("n", length $data) . $data; + $fh->send($data); + $recv_data = substr $recv_data, 2 + $len; + goto again if length $recv_data; + } + } + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_fetch_timeout.t b/tests/nginx-tests/nginx-tests/js_fetch_timeout.t new file mode 100644 index 0000000000..486656d66c --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_fetch_timeout.t @@ -0,0 +1,119 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, fetch method timeout. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /normal_timeout { + js_content test.timeout_test; + } + + location /short_timeout { + js_fetch_timeout 200ms; + js_content test.timeout_test; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location /normal_reply { + js_content test.normal_reply; + } + + location /delayed_reply { + js_content test.delayed_reply; + } + } +} + +EOF + +my $p1 = port(8081); + +$t->write_file('test.js', < ngx.fetch(v))); + + let bs = rs.map(v => ({s: v.status, v: v.value ? v.value.headers.X + : v.reason})); + + r.return(200, njs.dump(bs)); + } + + function normal_reply(r) { + r.headersOut.X = 'N'; + r.return(200); + } + + function delayed_reply(r) { + r.headersOut.X = 'D'; + setTimeout((r) => { r.return(200); }, 250, r, 0); + } + + export default {njs: test_njs, timeout_test, normal_reply, delayed_reply}; +EOF + +$t->try_run('no js_fetch_timeout')->plan(2); + +############################################################################### + +like(http_get('/normal_timeout'), + qr/\[\{s:'fulfilled',v:'N'},\{s:'fulfilled',v:'D'}]$/s, + 'normal timeout'); +like(http_get('/short_timeout'), + qr/\[\{s:'fulfilled',v:'N'},\{s:'rejected',v:Error: read timed out}]$/s, + 'short timeout'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_fetch_verify.t b/tests/nginx-tests/nginx-tests/js_fetch_verify.t new file mode 100644 index 0000000000..d6bb1d9e3d --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_fetch_verify.t @@ -0,0 +1,192 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http njs module, fetch method, backend certificate verification. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_ssl/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + resolver 127.0.0.1:%%PORT_8981_UDP%%; + resolver_timeout 1s; + + location /njs { + js_content test.njs; + } + + location /https { + js_content test.https; + } + + location /https.verify_off { + js_content test.https; + js_fetch_verify off; + } + } + + server { + listen 127.0.0.1:8081 ssl; + server_name localhost; + + ssl_certificate localhost.crt; + ssl_certificate_key localhost.key; + } +} + +EOF + +my $p1 = port(8081); + +$t->write_file('test.js', < reply.text()) + .then(body => r.return(200, body)) + .catch(e => r.return(501, e.message)); + } + + export default {njs: test_njs, https}; +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('localhost') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->try_run('no js_fetch_verify')->plan(2); + +$t->run_daemon(\&dns_daemon, port(8981), $t); +$t->waitforfile($t->testdir . '/' . port(8981)); + +############################################################################### + +like(http_get('/https'), qr/connect failed/, 'fetch verify error'); +like(http_get('/https.verify_off'), qr/200 OK/, 'fetch verify off'); + +############################################################################### + +sub reply_handler { + my ($recv_data, $port, %extra) = @_; + + my (@name, @rdata); + + use constant NOERROR => 0; + use constant A => 1; + use constant IN => 1; + + # default values + + my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 3600); + + # decode name + + my ($len, $offset) = (undef, 12); + while (1) { + $len = unpack("\@$offset C", $recv_data); + last if $len == 0; + $offset++; + push @name, unpack("\@$offset A$len", $recv_data); + $offset += $len; + } + + $offset -= 1; + my ($id, $type, $class) = unpack("n x$offset n2", $recv_data); + + my $name = join('.', @name); + + if ($type == A) { + push @rdata, rd_addr($ttl, '127.0.0.1'); + } + + $len = @name; + pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata, + 0, 0, @name, $type, $class) . join('', @rdata); +} + +sub rd_addr { + my ($ttl, $addr) = @_; + + my $code = 'split(/\./, $addr)'; + + return pack 'n3N', 0xc00c, A, IN, $ttl if $addr eq ''; + + pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code); +} + +sub dns_daemon { + my ($port, $t) = @_; + + my ($data, $recv_data); + my $socket = IO::Socket::INET->new( + LocalAddr => '127.0.0.1', + LocalPort => $port, + Proto => 'udp', + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + # signal we are ready + + open my $fh, '>', $t->testdir() . '/' . $port; + close $fh; + + while (1) { + $socket->recv($recv_data, 65536); + $data = reply_handler($recv_data, $port); + $socket->send($data); + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_header_filter.t b/tests/nginx-tests/nginx-tests/js_header_filter.t new file mode 100644 index 0000000000..aecac34d9e --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_header_filter.t @@ -0,0 +1,93 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, header filter. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /filter/ { + js_header_filter test.filter; + proxy_pass http://127.0.0.1:8081/; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location / { + add_header Set-Cookie "BB"; + add_header Set-Cookie "CCCC"; + + return 200; + } + } +} + +EOF + +$t->write_file('test.js', <v.length > len); + } + + export default {njs: test_njs, filter}; + +EOF + +$t->try_run('no njs header filter')->plan(2); + +############################################################################### + +like(http_get('/filter/?len=1'), qr/Set-Cookie: BB.*Set-Cookie: CCCC.*/ms, + 'all');; +unlike(http_get('/filter/?len=3'), qr/Set-Cookie: BB/, + 'filter'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_header_filter_if.t b/tests/nginx-tests/nginx-tests/js_header_filter_if.t new file mode 100644 index 0000000000..a6ab3c42f1 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_header_filter_if.t @@ -0,0 +1,95 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, header filter, if context. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location / { + if ($arg_name ~ "add") { + js_header_filter test.add; + } + + js_header_filter test.add2; + + proxy_pass http://127.0.0.1:8081/; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location / { + return 200; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs header filter')->plan(2); + +############################################################################### + +like(http_get('/?name=add'), qr/Foo: bar/, 'header filter if'); +like(http_get('/'), qr/Bar: xxx/, 'header filter'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_headers.t b/tests/nginx-tests/nginx-tests/js_headers.t new file mode 100644 index 0000000000..896a95fca3 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_headers.t @@ -0,0 +1,550 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, working with headers. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http charset/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_set $test_foo_in test.foo_in; + js_set $test_ifoo_in test.ifoo_in; + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /content_length { + js_content test.content_length; + } + + location /content_length_arr { + js_content test.content_length_arr; + } + + location /content_length_keys { + js_content test.content_length_keys; + } + + location /content_type { + charset windows-1251; + + default_type text/plain; + js_content test.content_type; + } + + location /content_type_arr { + charset windows-1251; + + default_type text/plain; + js_content test.content_type_arr; + } + + location /content_encoding { + js_content test.content_encoding; + } + + location /content_encoding_arr { + js_content test.content_encoding_arr; + } + + location /headers_list { + js_content test.headers_list; + } + + location /foo_in { + return 200 $test_foo_in; + } + + location /ifoo_in { + return 200 $test_ifoo_in; + } + + location /hdr_in { + js_content test.hdr_in; + } + + location /raw_hdr_in { + js_content test.raw_hdr_in; + } + + location /hdr_out { + js_content test.hdr_out; + } + + location /raw_hdr_out { + js_content test.raw_hdr_out; + } + + location /hdr_out_array { + js_content test.hdr_out_array; + } + + location /hdr_out_set_cookie { + js_content test.hdr_out_set_cookie; + } + + location /hdr_out_single { + js_content test.hdr_out_single; + } + + location /ihdr_out { + js_content test.ihdr_out; + } + + location /hdr_sorted_keys { + js_content test.hdr_sorted_keys; + } + + location /hdr_out_special_set { + js_content test.hdr_out_special_set; + } + + location /copy_subrequest_hdrs { + js_content test.copy_subrequest_hdrs; + } + + location = /subrequest { + internal; + + js_content test.subrequest; + } + } +} + +EOF + +$t->write_file('test.js', <= 0x000705) { + var clength = r.headersOut['Content-Length']; + if (clength !== undefined) { + r.return(500, `Content-Length "\${clength}" is not empty`); + return; + } + } + + delete r.headersOut['Content-Length']; + r.headersOut['Content-Length'] = ''; + r.headersOut['Content-Length'] = 3; + delete r.headersOut['Content-Length']; + r.headersOut['Content-Length'] = 3; + r.sendHeader(); + r.send('XXX'); + r.finish(); + } + + function content_length_arr(r) { + r.headersOut['Content-Length'] = [5]; + r.headersOut['Content-Length'] = []; + r.headersOut['Content-Length'] = [4,3]; + r.sendHeader(); + r.send('XXX'); + r.finish(); + } + + function content_length_keys(r) { + r.headersOut['Content-Length'] = 3; + var in_keys = Object.keys(r.headersOut).some(v=>v=='Content-Length'); + r.return(200, `B:\${in_keys}`); + } + + function content_type(r) { + if (njs.version_number >= 0x000705) { + var ctype = r.headersOut['Content-Type']; + if (ctype !== undefined) { + r.return(500, `Content-Type "\${ctype}" is not empty`); + return; + } + } + + delete r.headersOut['Content-Type']; + r.headersOut['Content-Type'] = 'text/xml'; + r.headersOut['Content-Type'] = ''; + r.headersOut['Content-Type'] = 'text/xml; charset='; + delete r.headersOut['Content-Type']; + r.headersOut['Content-Type'] = 'text/xml; charset=utf-8'; + r.headersOut['Content-Type'] = 'text/xml; charset="utf-8"'; + var in_keys = Object.keys(r.headersOut).some(v=>v=='Content-Type'); + r.return(200, `B:\${in_keys}`); + } + + function content_type_arr(r) { + r.headersOut['Content-Type'] = ['text/html']; + r.headersOut['Content-Type'] = []; + r.headersOut['Content-Type'] = [ 'text/xml', 'text/html']; + r.return(200); + } + + function content_encoding(r) { + if (njs.version_number >= 0x000705) { + var ce = r.headersOut['Content-Encoding']; + if (ce !== undefined) { + r.return(500, `Content-Encoding "\${ce}" is not empty`); + return; + } + } + + delete r.headersOut['Content-Encoding']; + r.headersOut['Content-Encoding'] = ''; + r.headersOut['Content-Encoding'] = 'test'; + delete r.headersOut['Content-Encoding']; + r.headersOut['Content-Encoding'] = 'gzip'; + r.return(200); + } + + function content_encoding_arr(r) { + r.headersOut['Content-Encoding'] = 'test'; + r.headersOut['Content-Encoding'] = []; + r.headersOut['Content-Encoding'] = ['test', 'gzip']; + r.return(200); + } + + function headers_list(r) { + for (var h in {a:1, b:2, c:3}) { + r.headersOut[h] = h; + } + + delete r.headersOut.b; + r.headersOut.d = 'd'; + + var out = ""; + for (var h in r.headersOut) { + out += h + ":"; + } + + r.return(200, out); + } + + function hdr_in(r) { + var s = '', h; + for (h in r.headersIn) { + s += `\${h.toLowerCase()}: \${r.headersIn[h]}\n`; + } + + r.return(200, s); + } + + function raw_hdr_in(r) { + var filtered = r.rawHeadersIn + .filter(v=>v[0].toLowerCase() == r.args.filter); + r.return(200, 'raw:' + filtered.map(v=>v[1]).join('|')); + } + + function hdr_sorted_keys(r) { + var s = ''; + var hdr = r.args.in ? r.headersIn : r.headersOut; + + if (!r.args.in) { + r.headersOut.b = 'b'; + r.headersOut.c = 'c'; + r.headersOut.a = 'a'; + } + + r.return(200, Object.keys(hdr).sort()); + } + + function foo_in(r) { + return 'hdr=' + r.headersIn.foo; + } + + function ifoo_in(r) { + var s = '', h; + for (h in r.headersIn) { + if (h.substr(0, 3) == 'foo') { + s += r.headersIn[h]; + } + } + return s; + } + + function hdr_out(r) { + r.status = 200; + r.headersOut['Foo'] = r.args.foo; + + if (r.args.bar) { + r.headersOut['Bar'] = + r.headersOut[(r.args.bar == 'empty' ? 'Baz' :'Foo')] + } + + r.sendHeader(); + r.finish(); + } + + function raw_hdr_out(r) { + r.headersOut.a = ['foo', 'bar']; + r.headersOut.b = 'b'; + + var filtered = r.rawHeadersOut + .filter(v=>v[0].toLowerCase() == r.args.filter); + r.return(200, 'raw:' + filtered.map(v=>v[1]).join('|')); + } + + function hdr_out_array(r) { + if (!r.args.hidden) { + r.headersOut['Foo'] = [r.args.foo]; + r.headersOut['Foo'] = []; + r.headersOut['Foo'] = ['bar', r.args.foo]; + } + + if (r.args.scalar_set) { + r.headersOut['Foo'] = 'xxx'; + } + + r.return(200, `B:\${njs.dump(r.headersOut.foo)}`); + } + + function hdr_out_single(r) { + r.headersOut.ETag = ['a', 'b']; + r.return(200, `B:\${njs.dump(r.headersOut.etag)}`); + } + + function hdr_out_set_cookie(r) { + r.headersOut['Set-Cookie'] = []; + r.headersOut['Set-Cookie'] = ['a', 'b']; + delete r.headersOut['Set-Cookie']; + r.headersOut['Set-Cookie'] = 'e'; + r.headersOut['Set-Cookie'] = ['c', '', null, 'd', 'f']; + + r.return(200, `B:\${njs.dump(r.headersOut['Set-Cookie'])}`); + } + + function ihdr_out(r) { + r.status = 200; + r.headersOut['a'] = r.args.a; + r.headersOut['b'] = r.args.b; + + var s = '', h; + for (h in r.headersOut) { + s += r.headersOut[h]; + } + + r.sendHeader(); + r.send(s); + r.finish(); + } + + function hdr_out_special_set(r) { + r.headersOut['Foo'] = "xxx"; + r.headersOut['Content-Encoding'] = 'abc'; + + let ce = r.headersOut['Content-Encoding']; + r.return(200, `CE: \${ce}`); + } + + async function copy_subrequest_hdrs(r) { + let resp = await r.subrequest("/subrequest"); + + for (const h in resp.headersOut) { + r.headersOut[h] = resp.headersOut[h]; + } + + r.return(200, resp.responseText); + } + + function subrequest(r) { + r.headersOut['A'] = 'a'; + r.headersOut['Content-Encoding'] = 'ce'; + r.headersOut['B'] = 'b'; + r.headersOut['C'] = 'c'; + r.headersOut['D'] = 'd'; + r.headersOut['Set-Cookie'] = ['A', 'BB']; + r.headersOut['Content-Length'] = 3; + r.headersOut['Content-Type'] = 'ct'; + r.sendHeader(); + r.send('XXX'); + r.finish(); + } + + export default {njs:test_njs, content_length, content_length_arr, + content_length_keys, content_type, content_type_arr, + content_encoding, content_encoding_arr, headers_list, + hdr_in, raw_hdr_in, hdr_sorted_keys, foo_in, ifoo_in, + hdr_out, raw_hdr_out, hdr_out_array, hdr_out_single, + hdr_out_set_cookie, ihdr_out, hdr_out_special_set, + copy_subrequest_hdrs, subrequest}; + + +EOF + +$t->try_run('no njs')->plan(42); + +############################################################################### + +like(http_get('/content_length'), qr/Content-Length: 3/, + 'set Content-Length'); +like(http_get('/content_type'), qr/Content-Type: text\/xml; charset="utf-8"\r/, + 'set Content-Type'); +unlike(http_get('/content_type'), qr/Content-Type: text\/plain/, + 'set Content-Type 2'); +like(http_get('/content_encoding'), qr/Content-Encoding: gzip/, + 'set Content-Encoding'); +like(http_get('/headers_list'), qr/a:c:d/, 'headers list'); + +like(http_get('/ihdr_out?a=12&b=34'), qr/^1234$/m, 'r.headersOut iteration'); +like(http_get('/ihdr_out'), qr/\x0d\x0a?\x0d\x0a?$/m, 'r.send zero'); +like(http_get('/hdr_out?foo=12345'), qr/Foo: 12345/, 'r.headersOut'); +like(http_get('/hdr_out?foo=123&bar=copy'), qr/Bar: 123/, 'r.headersOut get'); +unlike(http_get('/hdr_out?bar=empty'), qr/Bar:/, 'r.headersOut empty'); +unlike(http_get('/hdr_out?foo='), qr/Foo:/, 'r.headersOut no value'); +unlike(http_get('/hdr_out?foo'), qr/Foo:/, 'r.headersOut no value 2'); + +like(http_get('/content_length_keys'), qr/B:true/, 'Content-Length in keys'); +like(http_get('/content_length_arr'), qr/Content-Length: 3/, + 'set Content-Length arr'); + +like(http_get('/content_type'), qr/B:true/, 'Content-Type in keys'); +like(http_get('/content_type_arr'), qr/Content-Type: text\/html/, + 'set Content-Type arr'); +like(http_get('/content_encoding_arr'), qr/Content-Encoding: gzip/, + 'set Content-Encoding arr'); + +like(http_get('/hdr_out_array?foo=12345'), qr/Foo: bar\r\nFoo: 12345/, + 'r.headersOut arr'); +like(http_get('/hdr_out_array'), qr/Foo: bar/, + 'r.headersOut arr last is empty'); +like(http_get('/hdr_out_array?foo=abc'), qr/B:bar,\s?abc/, + 'r.headersOut get'); +like(http_get('/hdr_out_array'), qr/B:bar/, 'r.headersOut get2'); +like(http_get('/hdr_out_array?hidden=1'), qr/B:undefined/, + 'r.headersOut get3'); +like(http_get('/hdr_out_array?scalar_set=1'), qr/B:xxx/, + 'r.headersOut scalar set'); +like(http_get('/hdr_out_single'), qr/ETag: a\r\nETag: b/, + 'r.headersOut single'); +like(http_get('/hdr_out_single'), qr/B:a/, + 'r.headersOut single get'); +like(http_get('/hdr_out_set_cookie'), qr/Set-Cookie: c\r\nSet-Cookie: d/, + 'set_cookie'); +like(http_get('/hdr_out_set_cookie'), qr/B:\['c','d','f']/, + 'set_cookie2'); +unlike(http_get('/hdr_out_set_cookie'), qr/Set-Cookie: [abe]/, + 'set_cookie3'); + +like(http( + 'GET /hdr_in HTTP/1.0' . CRLF + . 'Cookie: foo' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/cookie: foo/, 'r.headersIn cookie'); + +like(http( + 'GET /hdr_in HTTP/1.0' . CRLF + . 'X-Forwarded-For: foo' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/x-forwarded-for: foo/, 'r.headersIn xff'); + +like(http( + 'GET /hdr_in HTTP/1.0' . CRLF + . 'Cookie: foo1' . CRLF + . 'Cookie: foo2' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/cookie: foo1;\s?foo2/, 'r.headersIn cookie2'); + +like(http( + 'GET /hdr_in HTTP/1.0' . CRLF + . 'X-Forwarded-For: foo1' . CRLF + . 'X-Forwarded-For: foo2' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/x-forwarded-for: foo1,\s?foo2/, 'r.headersIn xff2'); + +like(http( + 'GET /hdr_in HTTP/1.0' . CRLF + . 'ETag: bar1' . CRLF + . 'ETag: bar2' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/etag: bar1(?!,\s?bar2)/, 'r.headersIn duplicate single'); + +like(http( + 'GET /hdr_in HTTP/1.0' . CRLF + . 'Content-Type: bar1' . CRLF + . 'Content-Type: bar2' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/content-type: bar1(?!,\s?bar2)/, 'r.headersIn duplicate single 2'); + +like(http( + 'GET /hdr_in HTTP/1.0' . CRLF + . 'Foo: bar1' . CRLF + . 'Foo: bar2' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/foo: bar1,\s?bar2/, 'r.headersIn duplicate generic'); + +like(http( + 'GET /raw_hdr_in?filter=foo HTTP/1.0' . CRLF + . 'foo: bar1' . CRLF + . 'Foo: bar2' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/raw: bar1|bar2/, 'r.rawHeadersIn'); + +like(http_get('/raw_hdr_out?filter=a'), qr/raw: foo|bar/, 'r.rawHeadersOut'); + +like(http( + 'GET /hdr_sorted_keys?in=1 HTTP/1.0' . CRLF + . 'Cookie: foo1' . CRLF + . 'Accept: */*' . CRLF + . 'Cookie: foo2' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/Accept,Cookie,Host/, 'r.headersIn sorted keys'); + +like(http( + 'GET /hdr_sorted_keys HTTP/1.0' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/a,b,c/, 'r.headersOut sorted keys'); + +TODO: { +local $TODO = 'not yet' + unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.7.6'; + +like(http_get('/hdr_out_special_set'), qr/CE: abc/, + 'r.headerOut special set'); + +like(http_get('/copy_subrequest_hdrs'), + qr/A: a.*B: b.*C: c.*D: d.*Set-Cookie: A.*Set-Cookie: BB/s, + 'subrequest copy'); + +like(http_get('/copy_subrequest_hdrs'), + qr/Content-Type: ct.*Content-Encoding: ce.*Content-Length: 3/s, + 'subrequest copy special'); + +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_import.t b/tests/nginx-tests/nginx-tests/js_import.t new file mode 100644 index 0000000000..52ee795404 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_import.t @@ -0,0 +1,108 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (c) Nginx, Inc. + +# Tests for http njs module, js_import directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_set $test foo.bar.p; + + js_import lib.js; + js_import fun.js; + js_import foo from ./main.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content foo.version; + } + + location /test_foo { + js_content foo.test; + } + + location /test_lib { + js_content lib.test; + } + + location /test_fun { + js_content fun; + } + + location /test_var { + return 200 $test; + } + } +} + +EOF + +$t->write_file('lib.js', <write_file('fun.js', <write_file('main.js', <try_run('no njs available')->plan(4); + +############################################################################### + +like(http_get('/test_foo'), qr/MAIN-TEST/s, 'foo.test'); +like(http_get('/test_lib'), qr/LIB-TEST/s, 'lib.test'); +like(http_get('/test_fun'), qr/FUN-TEST/s, 'fun'); +like(http_get('/test_var'), qr/P-TEST/s, 'foo.bar.p'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_import2.t b/tests/nginx-tests/nginx-tests/js_import2.t new file mode 100644 index 0000000000..cd29d2dc34 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_import2.t @@ -0,0 +1,127 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (c) Nginx, Inc. + +# Tests for http njs module, js_import directive in server | location contexts. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + js_set $test foo.bar.p; + + js_import foo from main.js; + + location /njs { + js_content foo.version; + } + + location /test_foo { + js_content foo.test; + } + + location /test_lib { + js_import lib.js; + js_content lib.test; + } + + location /test_fun { + js_import fun.js; + js_content fun; + } + + location /test_var { + return 200 $test; + } + + location /proxy { + proxy_pass http://127.0.0.1:8081/; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location /test_fun { + js_import fun.js; + js_content fun; + } + } +} + +EOF + +$t->write_file('lib.js', <write_file('fun.js', <write_file('main.js', <try_run('no njs available')->plan(5); + +############################################################################### + +like(http_get('/test_foo'), qr/MAIN-TEST/s, 'foo.test'); +like(http_get('/test_lib'), qr/LIB-TEST/s, 'lib.test'); +like(http_get('/test_fun'), qr/FUN-TEST/s, 'fun'); +like(http_get('/proxy/test_fun'), qr/FUN-TEST/s, 'proxy fun'); +like(http_get('/test_var'), qr/P-TEST/s, 'foo.bar.p'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_internal_redirect.t b/tests/nginx-tests/nginx-tests/js_internal_redirect.t new file mode 100644 index 0000000000..7b978e3f14 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_internal_redirect.t @@ -0,0 +1,112 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, internalRedirect method. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /test { + js_content test.redirect; + } + + location /redirect { + internal; + return 200 redirect$arg_b; + } + + location @named { + return 200 named; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs available')->plan(5); + +############################################################################### + +like(http_get('/test'), qr/redirect/s, 'redirect'); +like(http_get('/test?a=A'), qr/redirectA/s, 'redirect with args'); +like(http_get('/test?dest=named'), qr/named/s, 'redirect to named location'); + +TODO: { +local $TODO = 'not yet' + unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.7.4'; + +like(http_get('/test?unsafe=1'), qr/500 Internal Server/s, + 'unsafe redirect'); +like(http_get('/test?quoted=1'), qr/200 .*redirect/s, + 'quoted redirect'); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_modules.t b/tests/nginx-tests/nginx-tests/js_modules.t new file mode 100644 index 0000000000..21581b4ac8 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_modules.t @@ -0,0 +1,84 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, ES6 import, export. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /test { + js_content test.test; + } + } +} + +EOF + +$t->write_file('test.js', <write_file('module.js', <try_run('no njs modules')->plan(2); + +############################################################################### + +like(http_get('/test?fun=sum&a=3&b=4'), qr/7/s, 'test sum'); +like(http_get('/test?fun=prod&a=3&b=4'), qr/12/s, 'test prod'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_ngx.t b/tests/nginx-tests/nginx-tests/js_ngx.t new file mode 100644 index 0000000000..c6271f086f --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_ngx.t @@ -0,0 +1,94 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, ngx object. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /log { + js_content test.log; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs ngx')->plan(3); + +############################################################################### + +http_get('/log?level=INFO&text=FOO'); +http_get('/log?level=WARN&text=BAR'); +http_get('/log?level=ERR&text=BAZ'); + +$t->stop(); + +like($t->read_file('error.log'), qr/\[info\].*ngx.log:FOO/, 'ngx.log info'); +like($t->read_file('error.log'), qr/\[warn\].*ngx.log:BAR/, 'ngx.log warn'); +like($t->read_file('error.log'), qr/\[error\].*ngx.log:BAZ/, 'ngx.log err'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_object.t b/tests/nginx-tests/nginx-tests/js_object.t new file mode 100644 index 0000000000..97e778a2a0 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_object.t @@ -0,0 +1,137 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, request object. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /to_string { + js_content test.to_string; + } + + location /define_prop { + js_content test.define_prop; + } + + location /in_operator { + js_content test.in_operator; + } + + location /redefine_bind { + js_content test.redefine_bind; + } + + location /redefine_proxy { + js_content test.redefine_proxy; + } + + location /redefine_proto { + js_content test.redefine_proto; + } + + location /get_own_prop_descs { + js_content test.get_own_prop_descs; + } + } +} + +EOF + +$t->write_file('test.js', <v in r.headersIn) + .toString() === 'true,false'); + } + + function redefine_bind(r) { + r.return = r.return.bind(r, 200); + r.return('redefine_bind'); + } + + function redefine_proxy(r) { + r.return_orig = r.return; + r.return = function (body) { this.return_orig(200, body);} + r.return('redefine_proxy'); + } + + function redefine_proto(r) { + r[0] = 'a'; + r[1] = 'b'; + r.length = 2; + Object.setPrototypeOf(r, Array.prototype); + r.return(200, r.join('|')); + } + + function get_own_prop_descs(r) { + r.return(200, + Object.getOwnPropertyDescriptors(r)['log'].value === r.log); + } + + export default {to_string, define_prop, in_operator, redefine_bind, + redefine_proxy, redefine_proto, get_own_prop_descs}; + +EOF + +$t->try_run('no njs request object')->plan(7); + +############################################################################### + +like(http_get('/to_string'), qr/\[object Request\]/, 'toString'); +like(http_get('/define_prop'), qr/Foo: bar/, 'define_prop'); +like(http( + 'GET /in_operator HTTP/1.0' . CRLF + . 'Foo: foo' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/true/, 'in_operator'); +like(http_get('/redefine_bind'), qr/redefine_bind/, 'redefine_bind'); +like(http_get('/redefine_proxy'), qr/redefine_proxy/, 'redefine_proxy'); +like(http_get('/redefine_proto'), qr/a|b/, 'redefine_proto'); +like(http_get('/get_own_prop_descs'), qr/true/, 'get_own_prop_descs'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_paths.t b/tests/nginx-tests/nginx-tests/js_paths.t new file mode 100644 index 0000000000..98d8751258 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_paths.t @@ -0,0 +1,110 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, js_path directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_path "%%TESTDIR%%/lib1"; + js_path "lib2"; + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /test { + js_content test.test; + } + + location /test2 { + js_content test.test2; + } + } +} + +EOF + +$t->write_file('test.js', <testdir(); + +mkdir("$d/lib1"); +mkdir("$d/lib2"); + +$t->write_file('lib1/module1.js', <write_file('lib2/module2.js', <try_run('no njs available')->plan(4); + +############################################################################### + +like(http_get('/test?fun=sum&a=3&b=4'), qr/7/s, 'test sum'); +like(http_get('/test?fun=prod&a=3&b=4'), qr/12/s, 'test prod'); +like(http_get('/test2?a=3&b=4'), qr/34/s, 'test2'); +like(http_get('/test2?a=A&b=B'), qr/AB/s, 'test2 relative'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_preload_object.t b/tests/nginx-tests/nginx-tests/js_preload_object.t new file mode 100644 index 0000000000..407e97fe97 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_preload_object.t @@ -0,0 +1,181 @@ +#!/usr/bin/perl + +# (C) Vadim Zhestikov +# (C) Nginx, Inc. + +# Tests for http njs module, js_preload_object directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_preload_object g1 from g.json; + js_preload_object ga from ga.json; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + js_import lib.js; + js_preload_object lx from l.json; + + location /test { + js_content lib.test; + } + + location /test_query { + js_import lib1.js; + js_content lib1.query; + } + + location /test_query_preloaded { + js_import lib1.js; + js_preload_object l.json; + js_content lib1.query; + } + + location /test_var { + js_set $test_var lib.test_var; + return 200 $test_var; + } + + location /test_mutate { + js_content lib.mutate; + } + + location /test_no_suffix { + js_preload_object gg from no_suffix; + js_content lib.suffix; + } + } +} + +EOF + +$t->write_file('lib.js', <write_file('lib1.js', < a[v], globalThis); + + } catch (e) { + res = e.message; + } + + r.return(200, njs.dump(res)); + } + + export default {query}; + +EOF + +$t->write_file('g.json', + '{"a":1, "b":[1,2,"element",4,5], "c":{"prop":[{"a":2}]}}'); +$t->write_file('ga.json', '"ga loaded"'); +$t->write_file('l.json', '"l loaded"'); +$t->write_file('no_suffix', '"no_suffix loaded"'); + +$t->try_run('no js_preload_object available')->plan(12); + +############################################################################### + +like(http_get('/test'), qr/ga loaded 2 l loaded/s, 'direct query'); +like(http_get('/test_query?path=l'), qr/undefined/s, 'unreferenced'); +like(http_get('/test_query_preloaded?path=l'), qr/l loaded/s, + 'reference preload'); +like(http_get('/test_query?path=g1.b.1'), qr/2/s, 'complex query'); +like(http_get('/test_var'), qr/element/s, 'var reference'); + +like(http_get('/test_mutate?method=set_obj'), qr/Cannot assign to read-only/s, + 'preload_object props are const (object)'); +like(http_get('/test_mutate?method=set_arr'), qr/Cannot assign to read-only/s, + 'preload_object props are const (array)'); +like(http_get('/test_mutate?method=add_obj'), qr/Cannot add property "xxx"/s, + 'preload_object props are not extensible (object)'); +like(http_get('/test_mutate?method=add_arr'), qr/Cannot add property "10"/s, + 'preload_object props are not extensible (array)'); +like(http_get('/test_mutate?method=del_obj'), qr/Cannot delete property "a"/s, + 'preload_object props are not deletable (object)'); +like(http_get('/test_mutate?method=del_arr'), qr/Cannot delete property "0"/s, + 'preload_object props are not deletable (array)'); + +like(http_get('/test_no_suffix'), qr/no_suffix loaded/s, + 'load without suffix'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_promise.t b/tests/nginx-tests/nginx-tests/js_promise.t new file mode 100644 index 0000000000..f6084e9918 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_promise.t @@ -0,0 +1,201 @@ +#!/usr/bin/perl + +# (C) Nginx, Inc. + +# Promise tests for http njs module. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /promise { + js_content test.promise; + } + + location /promise_throw { + js_content test.promise_throw; + } + + location /promise_pure { + js_content test.promise_pure; + } + + location /timeout { + js_content test.timeout; + } + + location /sub_token { + js_content test.sub_token; + } + } +} + +EOF + +$t->write_file('test.js', < { + var data = JSON.parse(reply.responseText); + + if (data['token'] !== "a") { + throw new Error('token is not "a"'); + } + + return data['token']; + }) + .then(token => { + promisified_subrequest(r, '/sub_token', 'code=200&token=b') + .then(reply => { + var data = JSON.parse(reply.responseText); + + r.return(200, '{"token": "' + data['token'] + '"}'); + }) + .catch(() => { + throw new Error("failed promise() test"); + }); + }) + .catch(() => { + r.return(500); + }); + } + + function promise_throw(r) { + promisified_subrequest(r, '/sub_token', 'code=200&token=x') + .then(reply => { + var data = JSON.parse(reply.responseText); + + if (data['token'] !== "a") { + throw data['token']; + } + + return data['token']; + }) + .then(() => { + r.return(500); + }) + .catch(token => { + r.return(200, '{"token": "' + token + '"}'); + }); + } + + function promise_pure(r) { + var count = 0; + + Promise.resolve(true) + .then(() => count++) + .then(() => not_exist_ref) + .finally(() => count++) + .catch(() => { + r.return((count != 2) ? 500 : 200); + }); + } + + function timeout(r) { + promisified_subrequest(r, '/sub_token', 'code=200&token=R') + .then(reply => JSON.parse(reply.responseText)) + .then(data => { + setTimeout(timeout_cb, 50, r, '/sub_token', 'code=200&token=T'); + return data; + }) + .then(data => { + setTimeout(timeout_cb, 1, r, '/sub_token', 'code=200&token=' + + data['token']); + }) + .catch(() => { + r.return(500); + }); + } + + function timeout_cb(r, url, args) { + promisified_subrequest(r, url, args) + .then(reply => { + if (global_token == '') { + var data = JSON.parse(reply.responseText); + + global_token = data['token']; + + r.return(200, '{"token": "' + data['token'] + '"}'); + } + }) + .catch(() => { + r.return(500); + }); + } + + function promisified_subrequest(r, uri, args) { + return new Promise((resolve, reject) => { + r.subrequest(uri, args, (reply) => { + if (reply.status < 400) { + resolve(reply); + } else { + reject(reply); + } + }); + }) + } + + function sub_token(r) { + var code = r.variables.arg_code; + var token = r.variables.arg_token; + + r.return(parseInt(code), '{"token": "'+ token +'"}'); + } + + export default {njs:test_njs, promise, promise_throw, promise_pure, + timeout, sub_token}; + +EOF + +$t->try_run('no njs available')->plan(4); + +############################################################################### + +like(http_get('/promise'), qr/{"token": "b"}/, "Promise"); +like(http_get('/promise_throw'), qr/{"token": "x"}/, "Promise throw and catch"); +like(http_get('/timeout'), qr/{"token": "R"}/, "Promise with timeout"); +like(http_get('/promise_pure'), qr/200 OK/, "events handling"); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_request_body.t b/tests/nginx-tests/nginx-tests/js_request_body.t new file mode 100644 index 0000000000..350e7fb830 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_request_body.t @@ -0,0 +1,110 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, r.requestText method. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /body { + js_content test.body; + } + + location /in_file { + client_body_in_file_only on; + js_content test.body; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs request body')->plan(3); + +############################################################################### + +like(http_post('/body'), qr/REQ-BODY/, 'request body'); +like(http_post('/in_file'), qr/request body is in a file/, + 'request body in file'); +like(http_post_big('/body'), qr/200.*^(1234567890){1024}$/ms, + 'request body big'); + +############################################################################### + +sub http_post { + my ($url, %extra) = @_; + + my $p = "POST $url HTTP/1.0" . CRLF . + "Host: localhost" . CRLF . + "Content-Length: 8" . CRLF . + CRLF . + "REQ-BODY"; + + return http($p, %extra); +} + +sub http_post_big { + my ($url, %extra) = @_; + + my $p = "POST $url HTTP/1.0" . CRLF . + "Host: localhost" . CRLF . + "Content-Length: 10240" . CRLF . + CRLF . + ("1234567890" x 1024); + + return http($p, %extra); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_return.t b/tests/nginx-tests/nginx-tests/js_return.t new file mode 100644 index 0000000000..2cc32f7433 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_return.t @@ -0,0 +1,73 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http njs module, return method. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use Config; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + js_content test.returnf; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs return')->plan(5); + +############################################################################### + +like(http_get('/?c=200'), qr/200 OK.*\x0d\x0a?\x0d\x0a?$/s, 'return code'); +like(http_get('/?c=200&t=SEE-THIS'), qr/200 OK.*^SEE-THIS$/ms, 'return text'); +like(http_get('/?c=301&t=path'), qr/ 301 .*Location: path/s, 'return redirect'); +like(http_get('/?c=404'), qr/404 Not.*html/s, 'return error page'); +like(http_get('/?c=inv'), qr/ 500 /, 'return invalid'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_subrequests.t b/tests/nginx-tests/nginx-tests/js_subrequests.t new file mode 100644 index 0000000000..a2007fa5e9 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_subrequests.t @@ -0,0 +1,636 @@ +#!/usr/bin/perl +# +# (C) Dmitry Volyntsev. +# (C) Nginx, Inc. + +# Tests for subrequests in http njs module. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { require JSON::PP; }; +plan(skip_all => "JSON::PP not installed") if $@; + +my $t = Test::Nginx->new()->has(qw/http rewrite proxy cache/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + proxy_cache_path %%TESTDIR%%/cache1 + keys_zone=ON:1m use_temp_path=on; + + js_import test.js; + + js_set $async_var test.async_var; + js_set $subrequest_var test.subrequest_var; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /sr { + js_content test.sr; + } + + location /sr_pr { + js_content test.sr_pr; + } + + location /sr_args { + js_content test.sr_args; + } + + location /sr_options_args { + js_content test.sr_options_args; + } + + location /sr_options_args_pr { + js_content test.sr_options_args_pr; + } + + location /sr_options_method { + js_content test.sr_options_method; + } + + location /sr_options_method_pr { + js_content test.sr_options_method_pr; + } + + location /sr_options_body { + js_content test.sr_options_body; + } + + location /sr_options_method_head { + js_content test.sr_options_method_head; + } + + location /sr_body { + js_content test.sr_body; + } + + location /sr_body_pr { + js_content test.sr_body_pr; + } + + location /sr_body_special { + js_content test.sr_body_special; + } + + location /sr_in_variable_handler { + set $_ $async_var; + js_content test.sr_in_variable_handler; + } + + location /sr_detached_in_variable_handler { + return 200 $subrequest_var; + } + + location /sr_async_var { + set $_ $async_var; + error_page 404 /return; + return 404; + } + + location /sr_error_page { + js_content test.sr_error_page; + } + + location /sr_js_in_subrequest { + js_content test.sr_js_in_subrequest; + } + + location /sr_js_in_subrequest_pr { + js_content test.sr_js_in_subrequest_pr; + } + + location /sr_file { + js_content test.sr_file; + } + + location /sr_cache { + js_content test.sr_cache; + } + + + location /sr_unavail { + js_content test.sr_unavail; + } + + location /sr_unavail_pr { + js_content test.sr_unavail_pr; + } + + location /sr_broken { + js_content test.sr_broken; + } + + location /sr_too_large { + js_content test.sr_too_large; + } + + location /sr_out_of_order { + js_content test.sr_out_of_order; + } + + location /sr_except_not_a_func { + js_content test.sr_except_not_a_func; + } + + location /sr_except_failed_to_convert_options_arg { + js_content test.sr_except_failed_to_convert_options_arg; + } + + location /sr_except_invalid_options_header_only { + js_content test.sr_except_invalid_options_header_only; + } + + location /sr_in_sr_callback { + js_content test.sr_in_sr_callback; + } + + location /sr_uri_except { + js_content test.sr_uri_except; + } + + + location /file/ { + alias %%TESTDIR%%/; + } + + location /p/ { + proxy_cache $arg_c; + proxy_pass http://127.0.0.1:8081/; + } + + location /daemon/ { + proxy_pass http://127.0.0.1:8082/; + } + + location /too_large/ { + subrequest_output_buffer_size 3; + proxy_pass http://127.0.0.1:8081/; + } + + location /sr_in_sr { + js_content test.sr_in_sr; + } + + location /unavail { + proxy_pass http://127.0.0.1:8084/; + } + + location /sr_parent { + js_content test.sr_parent; + } + + location /js_sub { + js_content test.js_sub; + } + + location /return { + return 200 '["$request_method"]'; + } + + location /error_page_404 { + return 404; + + error_page 404 /404.html; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location /sub1 { + add_header H $arg_h; + return 206 '{"a": {"b": 1}}'; + } + + location /sub2 { + return 404 '{"e": "msg"}'; + } + + location /method { + return 200 '["$request_method"]'; + } + + location /body { + js_content test.body; + } + + location /detached { + js_content test.detached; + } + + location /delayed { + js_content test.delayed; + } + } + + server { + listen 127.0.0.1:8084; + server_name localhost; + + return 444; + } +} + +EOF + +$t->write_file('test.js', < r.return(200, JSON.stringify({h:reply.headersOut.h}))) + } + + function sr_args(r) { + r.subrequest('/p/sub1', 'h=xxx', reply => { + r.return(200, JSON.stringify({h:reply.headersOut.h})); + }); + } + + function sr_options_args(r) { + r.subrequest('/p/sub1', {args:'h=xxx'}, reply => { + r.return(200, JSON.stringify({h:reply.headersOut.h})); + }); + } + + function sr_options_args_pr(r) { + r.subrequest('/p/sub1', {args:'h=xxx'}) + .then(reply => r.return(200, JSON.stringify({h:reply.headersOut.h}))) + } + + function sr_options_method(r) { + r.subrequest('/p/method', {method:r.args.m}, body_fwd_cb); + } + + function sr_options_method_pr(r) { + r.subrequest('/p/method', {method:r.args.m}) + .then(body_fwd_cb); + } + + function sr_options_body(r) { + r.subrequest('/p/body', {method:'POST', body:'["REQ-BODY"]'}, + body_fwd_cb); + } + + function sr_options_method_head(r) { + r.subrequest('/p/method', {method:'HEAD'}, reply => { + r.return(200, JSON.stringify({c:reply.status})); + }); + } + + function sr_body(r) { + r.subrequest('/p/sub1', body_fwd_cb); + } + + function sr_body_pr(r) { + r.subrequest('/p/sub1') + .then(body_fwd_cb); + } + + function sr_body_special(r) { + r.subrequest('/p/sub2', body_fwd_cb); + } + + function body(r) { + r.return(200, r.variables.request_body); + } + + function delayed(r) { + setTimeout(r => r.return(200), 100, r); + } + + function detached(r) { + var method = r.variables.request_method; + r.log(`DETACHED: \${method} args: \${r.variables.args}`); + + r.return(200); + } + + function sr_in_variable_handler(r) { + } + + function async_var(r) { + r.subrequest('/p/delayed', reply => { + r.return(200, JSON.stringify(["CB-VAR"])); + }); + + return ""; + } + + function sr_error_page(r) { + r.subrequest('/error_page_404') + .then(reply => {r.return(200, `reply.status:\${reply.status}`)}); + } + + function subrequest_var(r) { + r.subrequest('/p/detached', {detached:true}); + r.subrequest('/p/detached', {detached:true, args:'a=yyy', + method:'POST'}); + + return "subrequest_var"; + } + + function sr_file(r) { + r.subrequest('/file/t', body_fwd_cb); + } + + function sr_cache(r) { + r.subrequest('/p/t', body_fwd_cb); + } + + function sr_unavail(req) { + subrequest_fn(req, ['/unavail'], ['uri', 'status']); + } + + function sr_unavail_pr(req) { + subrequest_fn_pr(req, ['/unavail'], ['uri', 'status']); + } + + function sr_broken(r) { + r.subrequest('/daemon/unfinished', reply => { + r.return(200, JSON.stringify({code:reply.status})); + }); + } + + function sr_too_large(r) { + r.subrequest('/too_large/t', body_fwd_cb); + } + + function sr_in_sr(r) { + r.subrequest('/sr', body_fwd_cb); + } + + function sr_js_in_subrequest(r) { + r.subrequest('/js_sub', body_fwd_cb); + } + + function sr_js_in_subrequest_pr(r) { + r.subrequest('/js_sub') + .then(body_fwd_cb); + } + + function sr_in_sr_callback(r) { + r.subrequest('/return', function (reply) { + try { + reply.subrequest('/return'); + + } catch (err) { + r.return(200, JSON.stringify({e:err.message})); + return; + } + + r.return(200); + }); + } + + function sr_parent(r) { + try { + var parent = r.parent; + + } catch (err) { + r.return(200, JSON.stringify({e:err.message})); + return; + } + + r.return(200); + } + + function sr_out_of_order(r) { + subrequest_fn(r, ['/p/delayed', '/p/sub1', '/unknown'], + ['uri', 'status']); + } + + function collect(replies, props, total, reply) { + reply.log(`subrequest handler: \${reply.uri} status: \${reply.status}`) + + var rep = {}; + props.forEach(p => {rep[p] = reply[p]}); + + replies.push(rep); + + if (replies.length == total) { + reply.parent.return(200, JSON.stringify(replies)); + } + } + + function subrequest_fn(r, subs, props) { + var replies = []; + + subs.forEach(sr => + r.subrequest(sr, collect.bind(null, replies, + props, subs.length))); + } + + function subrequest_fn_pr(r, subs, props) { + var replies = []; + + subs.forEach(sr => r.subrequest(sr) + .then(collect.bind(null, replies, props, subs.length))); + } + + function sr_except_not_a_func(r) { + r.subrequest('/sub1', 'a=1', 'b'); + } + + let Failed = {get toConvert() { return {toString(){return {};}}}}; + + function sr_except_failed_to_convert_options_arg(r) { + r.subrequest('/sub1', {args:Failed.toConvert}, ()=>{}); + } + + function sr_uri_except(r) { + r.subrequest(Failed.toConvert, 'a=1', 'b'); + } + + function body_fwd_cb(r) { + r.parent.return(200, JSON.stringify(JSON.parse(r.responseText))); + } + + function js_sub(r) { + r.return(200, '["JS-SUB"]'); + } + + export default {njs:test_njs, sr, sr_pr, sr_args, sr_options_args, + sr_options_args_pr, sr_options_method, sr_options_method_pr, + sr_options_method_head, sr_options_body, sr_body, + sr_body_pr, sr_body_special, body, delayed, detached, + sr_in_variable_handler, async_var, sr_error_page, + subrequest_var, sr_file, sr_cache, sr_unavail, sr_parent, + sr_unavail_pr, sr_broken, sr_too_large, sr_in_sr, + sr_js_in_subrequest, sr_js_in_subrequest_pr, js_sub, + sr_in_sr_callback, sr_out_of_order, sr_except_not_a_func, + sr_uri_except, sr_except_failed_to_convert_options_arg}; + +EOF + +$t->write_file('t', '["SEE-THIS"]'); + +$t->try_run('no njs available')->plan(32); +$t->run_daemon(\&http_daemon); + +############################################################################### + +is(get_json('/sr'), '[{"status":404,"uri":"/p/sub2"}]', 'sr'); +is(get_json('/sr_args'), '{"h":"xxx"}', 'sr_args'); +is(get_json('/sr_options_args'), '{"h":"xxx"}', 'sr_options_args'); +is(get_json('/sr_options_method?m=POST'), '["POST"]', 'sr method POST'); +is(get_json('/sr_options_method?m=PURGE'), '["PURGE"]', 'sr method PURGE'); +is(get_json('/sr_options_body'), '["REQ-BODY"]', 'sr_options_body'); +is(get_json('/sr_options_method_head'), '{"c":200}', 'sr_options_method_head'); +is(get_json('/sr_body'), '{"a":{"b":1}}', 'sr_body'); +is(get_json('/sr_body_special'), '{"e":"msg"}', 'sr_body_special'); +is(get_json('/sr_in_variable_handler'), '["CB-VAR"]', 'sr_in_variable_handler'); +is(get_json('/sr_file'), '["SEE-THIS"]', 'sr_file'); +is(get_json('/sr_cache?c=1'), '["SEE-THIS"]', 'sr_cache'); +is(get_json('/sr_cache?c=1'), '["SEE-THIS"]', 'sr_cached'); +is(get_json('/sr_js_in_subrequest'), '["JS-SUB"]', 'sr_js_in_subrequest'); +is(get_json('/sr_unavail'), '[{"status":502,"uri":"/unavail"}]', + 'sr_unavail'); +is(get_json('/sr_out_of_order'), + '[{"status":404,"uri":"/unknown"},' . + '{"status":206,"uri":"/p/sub1"},' . + '{"status":200,"uri":"/p/delayed"}]', + 'sr_multi'); + +is(get_json('/sr_pr'), '{"h":"xxx"}', 'sr_promise'); +is(get_json('/sr_options_args_pr'), '{"h":"xxx"}', 'sr_options_args_pr'); +is(get_json('/sr_options_method_pr?m=PUT'), '["PUT"]', 'sr method PUT'); +is(get_json('/sr_body_pr'), '{"a":{"b":1}}', 'sr_body_pr'); +is(get_json('/sr_js_in_subrequest_pr'), '["JS-SUB"]', 'sr_js_in_subrequest_pr'); +is(get_json('/sr_unavail_pr'), '[{"status":502,"uri":"/unavail"}]', + 'sr_unavail_pr'); + +like(http_get('/sr_detached_in_variable_handler'), qr/subrequest_var/, + 'sr_detached_in_variable_handler'); + +like(http_get('/sr_error_page'), qr/reply\.status:404/, + 'sr_error_page'); + +http_get('/sr_broken'); +http_get('/sr_in_sr'); +http_get('/sr_in_variable_handler'); +http_get('/sr_async_var'); +http_get('/sr_too_large'); +http_get('/sr_except_not_a_func'); +http_get('/sr_except_failed_to_convert_options_arg'); +http_get('/sr_uri_except'); + +is(get_json('/sr_in_sr_callback'), + '{"e":"subrequest can only be created for the primary request"}', + 'subrequest for non-primary request'); + +$t->stop(); + +ok(index($t->read_file('error.log'), 'callback is not a function') > 0, + 'subrequest cb exception'); +ok(index($t->read_file('error.log'), 'failed to convert uri arg') > 0, + 'subrequest uri exception'); +ok(index($t->read_file('error.log'), 'failed to convert options.args') > 0, + 'subrequest invalid args exception'); +ok(index($t->read_file('error.log'), 'too big subrequest response') > 0, + 'subrequest too large body'); +ok(index($t->read_file('error.log'), 'subrequest creation failed') > 0, + 'subrequest creation failed'); +ok(index($t->read_file('error.log'), + 'js subrequest: failed to get the parent context') > 0, + 'zero parent ctx'); + +ok(index($t->read_file('error.log'), 'DETACHED') > 0, + 'detached subrequest'); + +############################################################################### + +sub recode { + my $json; + eval { $json = JSON::PP::decode_json(shift) }; + + if ($@) { + return ""; + } + + JSON::PP->new()->canonical()->encode($json); +} + +sub get_json { + http_get(shift) =~ /\x0d\x0a?\x0d\x0a?(.*)/ms; + recode($1); +} + +############################################################################### + +sub http_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . port(8082), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + my $headers = ''; + my $uri = ''; + + while (<$client>) { + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; + + if ($uri eq '/unfinished') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Transfer-Encoding: chunked" . CRLF . + "Content-Length: 100" . CRLF . + CRLF . + "unfinished" . CRLF; + close($client); + } + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_var.t b/tests/nginx-tests/nginx-tests/js_var.t new file mode 100644 index 0000000000..4de8236c0e --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_var.t @@ -0,0 +1,90 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, js_var directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + js_var $foo; + js_var $bar a:$arg_a; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /test { + js_content test.test; + } + + location /sub { + return 200 DONE; + } + + location /dest { + return 200 $bar; + } + } +} + +EOF + +$t->write_file('test.js', < { + r.variables.bar = reply.responseText; + r.internalRedirect('/dest'); + }); + + return; + } + + r.return(200, `V:\${r.variables[r.args.var]}`); + } + + export default {test}; + +EOF + +$t->try_run('no njs js_var')->plan(3); + +############################################################################### + +like(http_get('/test?var=bar&a=qq'), qr/200 OK.*V:a:qq$/s, 'default value'); +like(http_get('/test?var=foo'), qr/200 OK.*V:$/s, 'default empty value'); +like(http_get('/test?sub=1&var=bar&a=qq'), qr/200 OK.*DONE$/s, 'value set'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_var2.t b/tests/nginx-tests/nginx-tests/js_var2.t new file mode 100644 index 0000000000..5c59d9cc18 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_var2.t @@ -0,0 +1,90 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, js_var directive in server | location contexts. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + js_var $foo; + + location /test { + js_content test.test; + } + + location /sub { + return 200 DONE; + } + + location /dest { + js_var $bar a:$arg_a; + return 200 $bar; + } + } +} + +EOF + +$t->write_file('test.js', < { + r.variables.bar = reply.responseText; + r.internalRedirect('/dest'); + }); + + return; + } + + r.return(200, `V:\${r.variables[r.args.var]}`); + } + + export default {test}; + +EOF + +$t->try_run('no njs js_var')->plan(3); + +############################################################################### + +like(http_get('/test?var=bar&a=qq'), qr/200 OK.*V:a:qq$/s, 'default value'); +like(http_get('/test?var=foo'), qr/200 OK.*V:$/s, 'default empty value'); +like(http_get('/test?sub=1&var=bar&a=qq'), qr/200 OK.*DONE$/s, 'value set'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/js_variables.t b/tests/nginx-tests/nginx-tests/js_variables.t new file mode 100644 index 0000000000..f2481e0ba8 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/js_variables.t @@ -0,0 +1,95 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, setting nginx variables. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_set $test_var test.variable; + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + set $foo test.foo_orig; + + location /var_set { + return 200 $test_var$foo; + } + + location /content_set { + js_content test.content_set; + } + + location /not_found_set { + js_content test.not_found_set; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs')->plan(3); + +############################################################################### + +like(http_get('/var_set?a=bar'), qr/test_varbar/, 'var set'); +like(http_get('/content_set?a=bar'), qr/bar/, 'content set'); +like(http_get('/not_found_set'), qr/variable not found/, 'not found exception'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/lib/Test/Nginx.pm b/tests/nginx-tests/nginx-tests/lib/Test/Nginx.pm index d0ff472e91..42d330d056 100644 --- a/tests/nginx-tests/nginx-tests/lib/Test/Nginx.pm +++ b/tests/nginx-tests/nginx-tests/lib/Test/Nginx.pm @@ -12,7 +12,9 @@ use strict; use base qw/ Exporter /; our @EXPORT = qw/ log_in log_out http http_get http_head port /; -our @EXPORT_OK = qw/ http_gzip_request http_gzip_like http_start http_end /; +our @EXPORT_OK = qw/ + http_gzip_request http_gzip_like http_start http_end http_content +/; our %EXPORT_TAGS = ( gzip => [ qw/ http_gzip_request http_gzip_like / ] ); @@ -180,12 +182,12 @@ sub has_module($) { => '(?s)^(?!.*--without-stream_map_module)', stream_return => '(?s)^(?!.*--without-stream_return_module)', + stream_set + => '(?s)^(?!.*--without-stream_set_module)', stream_split_clients => '(?s)^(?!.*--without-stream_split_clients_module)', stream_ssl => '--with-stream_ssl_module', - stream_sni - => '--with-stream_sni', stream_upstream_hash => '(?s)^(?!.*--without-stream_upstream_hash_module)', stream_upstream_least_conn @@ -297,11 +299,8 @@ sub try_run($$) { return $self unless $@; if ($ENV{TEST_NGINX_VERBOSE}) { - my $path = $self->{_configure_args} =~ m!--error-log-path=(\S+)! - ? $1 : 'logs/error.log'; - $path = "$self->{_testdir}/$path" if index($path, '/'); - - open F, '<', $path or die "Can't open $path: $!"; + open F, '<', $self->{_testdir} . '/error.log' + or die "Can't open error.log: $!"; log_core($_) while (); close F; } @@ -343,7 +342,8 @@ sub run(;$) { my @globals = $self->{_test_globals} ? () : ('-g', "pid $testdir/nginx.pid; " . "error_log $testdir/error.log debug;"); - exec($NGINX, '-p', "$testdir/", '-c', 'nginx.conf', @globals), + exec($NGINX, '-p', "$testdir/", '-c', 'nginx.conf', + '-e', 'error.log', @globals) or die "Unable to exec(): $!\n"; } @@ -416,7 +416,7 @@ sub dump_config() { () : ('-g', "pid $testdir/nginx.pid; " . "error_log $testdir/error.log debug;"); my $command = "$NGINX -T -p $testdir/ -c nginx.conf " - . join(' ', @globals); + . "-e error.log " . join(' ', @globals); return qx/$command 2>&1/; } @@ -470,7 +470,7 @@ sub reload() { () : ('-g', "pid $testdir/nginx.pid; " . "error_log $testdir/error.log debug;"); system($NGINX, '-p', $testdir, '-c', "nginx.conf", - '-s', 'reload', @globals) == 0 + '-s', 'reload', '-e', 'error.log', @globals) == 0 or die "system() failed: $?\n"; } else { @@ -493,14 +493,37 @@ sub stop() { () : ('-g', "pid $testdir/nginx.pid; " . "error_log $testdir/error.log debug;"); system($NGINX, '-p', $testdir, '-c', "nginx.conf", - '-s', 'stop', @globals) == 0 + '-s', 'quit', '-e', 'error.log', @globals) == 0 or die "system() failed: $?\n"; } else { kill 'QUIT', $pid; } - waitpid($pid, 0); + my $exited; + + for (1 .. 900) { + $exited = waitpid($pid, WNOHANG) != 0; + last if $exited; + select undef, undef, undef, 0.1; + } + + if (!$exited) { + if ($^O eq 'MSWin32') { + my $testdir = $self->{_testdir}; + my @globals = $self->{_test_globals} ? + () : ('-g', "pid $testdir/nginx.pid; " + . "error_log $testdir/error.log debug;"); + system($NGINX, '-p', $testdir, '-c', "nginx.conf", + '-s', 'stop', '-e', 'error.log', @globals) == 0 + or die "system() failed: $?\n"; + + } else { + kill 'TERM', $pid; + } + + waitpid($pid, 0); + } $self->{_started} = 0; @@ -513,7 +536,19 @@ sub stop_daemons() { while ($self->{_daemons} && scalar @{$self->{_daemons}}) { my $p = shift @{$self->{_daemons}}; kill $^O eq 'MSWin32' ? 9 : 'TERM', $p; - waitpid($p, 0); + + my $exited; + + for (1 .. 50) { + $exited = waitpid($p, WNOHANG) != 0; + last if $exited; + select undef, undef, undef, 0.1; + } + + if (!$exited) { + kill $^O eq 'MSWin32' ? 9 : 'TERM', $p; + waitpid($p, 0); + } } return $self; @@ -548,6 +583,7 @@ sub write_file_expand($$) { $content =~ s/%%TEST_GLOBALS%%/$self->test_globals()/gmse; $content =~ s/%%TEST_GLOBALS_HTTP%%/$self->test_globals_http()/gmse; + $content =~ s/%%TEST_GLOBALS_STREAM%%/$self->test_globals_stream()/gmse; $content =~ s/%%TESTDIR%%/$self->{_testdir}/gms; $content =~ s/127\.0\.0\.1:(8\d\d\d)/'127.0.0.1:' . port($1)/gmse; @@ -689,6 +725,20 @@ sub test_globals_http() { $self->{_test_globals_http} = $s; } +sub test_globals_stream() { + my ($self) = @_; + + return $self->{_test_globals_stream} + if defined $self->{_test_globals_stream}; + + my $s = ''; + + $s .= $ENV{TEST_NGINX_GLOBALS_STREAM} + if $ENV{TEST_NGINX_GLOBALS_STREAM}; + + $self->{_test_globals_stream} = $s; +} + ############################################################################### sub log_core { @@ -836,12 +886,18 @@ sub http_content { } my $content = ''; + my $len = -1; + while ($body =~ /\G\x0d?\x0a?([0-9a-f]+)\x0d\x0a?/gcmsi) { - my $len = hex($1); + $len = hex($1); $content .= substr($body, pos($body), $len); pos($body) += $len; } + if ($len != 0) { + $content .= '[no-last-chunk]'; + } + return $content; } diff --git a/tests/nginx-tests/nginx-tests/lib/Test/Nginx/HTTP2.pm b/tests/nginx-tests/nginx-tests/lib/Test/Nginx/HTTP2.pm index 2e02baab83..37debc6da6 100644 --- a/tests/nginx-tests/nginx-tests/lib/Test/Nginx/HTTP2.pm +++ b/tests/nginx-tests/nginx-tests/lib/Test/Nginx/HTTP2.pm @@ -39,14 +39,6 @@ sub new { my $preface = defined $extra{preface} ? $extra{preface} : 'PRI * HTTP/2.0' . CRLF . CRLF . 'SM' . CRLF . CRLF; - if ($extra{proxy}) { - raw_write($s, $extra{proxy}); - } - - # preface - - raw_write($s, $preface); - my $self = bless { socket => $s, last_stream => -1, dynamic_encode => [ static_table() ], @@ -55,6 +47,14 @@ sub new { iws => 65535, conn_window => 65535, streams => {} }, $class; + if ($extra{proxy}) { + $self->raw_write($extra{proxy}); + } + + # preface + + $self->raw_write($preface); + return $self if $extra{pure}; # update windows, if any @@ -76,13 +76,13 @@ sub new { sub h2_ping { my ($self, $payload) = @_; - raw_write($self->{socket}, pack("x2C2x5a8", 8, 0x6, $payload)); + $self->raw_write(pack("x2C2x5a8", 8, 0x6, $payload)); } sub h2_rst { my ($self, $stream, $error) = @_; - raw_write($self->{socket}, pack("x2C2xNN", 4, 0x3, $stream, $error)); + $self->raw_write(pack("x2C2xNN", 4, 0x3, $stream, $error)); } sub h2_goaway { @@ -92,11 +92,11 @@ sub h2_goaway { my $buf = pack("x2C2xN3A*", $len, 0x7, $stream, $lstream, $err, $debug); my @bufs = map { - raw_write($self->{socket}, substr $buf, 0, $_, ""); + $self->raw_write(substr $buf, 0, $_, ""); select undef, undef, undef, 0.2; } @{$extra{split}}; - raw_write($self->{socket}, $buf); + $self->raw_write($buf); } sub h2_priority { @@ -105,14 +105,14 @@ sub h2_priority { $stream = 0 unless defined $stream; $dep = 0 unless defined $dep; $dep |= $extra{excl} << 31 if exists $extra{excl}; - raw_write($self->{socket}, pack("x2C2xNNC", 5, 0x2, $stream, $dep, $w)); + $self->raw_write(pack("x2C2xNNC", 5, 0x2, $stream, $dep, $w)); } sub h2_window { my ($self, $win, $stream) = @_; $stream = 0 unless defined $stream; - raw_write($self->{socket}, pack("x2C2xNN", 4, 0x8, $stream, $win)); + $self->raw_write(pack("x2C2xNN", 4, 0x8, $stream, $win)); } sub h2_settings { @@ -121,14 +121,14 @@ sub h2_settings { my $len = 6 * @pairs / 2; my $buf = pack_length($len) . pack "CCx4", 0x4, $ack ? 0x1 : 0x0; $buf .= pack "nN", splice @pairs, 0, 2 while @pairs; - raw_write($self->{socket}, $buf); + $self->raw_write($buf); } sub h2_unknown { my ($self, $payload) = @_; my $buf = pack_length(length($payload)) . pack("Cx5a*", 0xa, $payload); - raw_write($self->{socket}, $buf); + $self->raw_write($buf); } sub h2_continue { @@ -167,12 +167,12 @@ sub h2_body { $split = ref $extra->{split} && $extra->{split} || []; for (@$split) { - raw_write($self->{socket}, substr($buf, 0, $_, "")); + $self->raw_write(substr($buf, 0, $_, "")); return if $extra->{abort}; select undef, undef, undef, ($extra->{split_delay} || 0.2); } - raw_write($self->{socket}, $buf); + $self->raw_write($buf); } sub new_stream { @@ -268,12 +268,12 @@ sub new_stream { $split = ref $uri->{split} && $uri->{split} || []; for (@$split) { - raw_write($self->{socket}, substr($buf, 0, $_, "")); + $self->raw_write(substr($buf, 0, $_, "")); goto done if $uri->{abort}; select undef, undef, undef, ($uri->{split_delay} || 0.2); } - raw_write($self->{socket}, $buf); + $self->raw_write($buf); done: return $self->{last_stream}; } @@ -288,7 +288,7 @@ sub read { local $Data::Dumper::Terse = 1; while (1) { - $buf = raw_read($s, $buf, 9, $wait); + $buf = $self->raw_read($buf, 9, $wait); last if length $buf < 9; my $length = unpack_length($buf); @@ -299,7 +299,7 @@ sub read { substr($stream, 0, 1) = 0; $stream = unpack("N", pack("B32", $stream)); - $buf = raw_read($s, $buf, $length + 9, $wait); + $buf = $self->raw_read($buf, $length + 9, $wait); last if length($buf) < $length + 9; $buf = substr($buf, 9); @@ -321,6 +321,64 @@ sub read { return \@got; } +sub raw_read { + my ($self, $buf, $len, $timo) = @_; + $timo = 8 unless $timo; + my $got = ''; + my $s = $self->{socket}; + + while (length($buf) < $len && IO::Select->new($s)->can_read($timo)) { + $s->sysread($got, 16384) or last; + log_in($got); + $buf .= $got; + } + return $buf; +} + +sub raw_write { + my ($self, $message) = @_; + + if ($self->{chaining}) { + return add_chain($self, $message); + } + + my $s = $self->{socket}; + + local $SIG{PIPE} = 'IGNORE'; + + while (IO::Select->new($s)->can_write(0.4)) { + log_out($message); + my $n = $s->syswrite($message); + last unless $n; + $message = substr($message, $n); + last unless length $message; + } +} + +sub start_chain { + my ($self) = @_; + + $self->{chaining} = 1; +} + +sub add_chain { + my ($self, $buf) = @_; + + if ($self->{chained_buf}) { + $self->{chained_buf} .= $buf; + } else { + $self->{chained_buf} = $buf; + } +} + +sub send_chain { + my ($self) = @_; + + undef $self->{chaining}; + $self->raw_write($self->{chained_buf}) if $self->{chained_buf}; + undef $self->{chained_buf}; +} + ############################################################################### sub pack_body { @@ -380,9 +438,15 @@ sub headers { sub continuation { my ($ctx, $buf, $len, $flags) = @_; + my %payload; + $ctx->{headers} .= substr($buf, 0, $len); - return unless $flags & 0x4; - { headers => hunpack($ctx, $ctx->{headers}, length($ctx->{headers})) }; + $payload{promised} = $ctx->{promised} if $ctx->{promised}; + return \%payload unless $flags & 0x4; + + $ctx->{promised} = undef; + return { %payload, headers => hunpack($ctx, $ctx->{headers}, + length($ctx->{headers})) }; } sub data { @@ -406,10 +470,13 @@ sub settings { sub push_promise { my ($ctx, $buf, $len, $flags) = @_; + my %payload; $len -= 4; - { promised => unpack("N", $buf), - headers => hunpack($ctx, substr($buf, 4, $len), $len) }; + $ctx->{promised} = $payload{promised} = unpack("N", $buf); + $ctx->{headers} = substr($buf, 4, $len); + return \%payload unless $flags & 0x4; + return { %payload, headers => hunpack($ctx, $ctx->{headers}, $len) }; } sub ping { @@ -463,33 +530,6 @@ sub unpack_length { unpack 'N', pack 'xc3', unpack 'c3', $_[0]; } -sub raw_read { - my ($s, $buf, $len, $timo) = @_; - $timo = 5 unless $timo; - my $got = ''; - - while (length($buf) < $len && IO::Select->new($s)->can_read($timo)) { - $s->sysread($got, 16384) or last; - log_in($got); - $buf .= $got; - } - return $buf; -} - -sub raw_write { - my ($s, $message) = @_; - - local $SIG{PIPE} = 'IGNORE'; - - while (IO::Select->new($s)->can_write(0.4)) { - log_out($message); - my $n = $s->syswrite($message); - last unless $n; - $message = substr($message, $n); - last unless length $message; - } -} - sub new_socket { my ($port, %extra) = @_; my $npn = $extra{'npn'}; @@ -501,7 +541,7 @@ sub new_socket { eval { local $SIG{ALRM} = sub { die "timeout\n" }; local $SIG{PIPE} = sub { die "sigpipe\n" }; - alarm(2); + alarm(8); $s = IO::Socket::INET->new( Proto => 'tcp', PeerAddr => "127.0.0.1:$port", diff --git a/tests/nginx-tests/nginx-tests/lib/Test/Nginx/IMAP.pm b/tests/nginx-tests/nginx-tests/lib/Test/Nginx/IMAP.pm index 0d4aadca2b..7ff55d07bf 100644 --- a/tests/nginx-tests/nginx-tests/lib/Test/Nginx/IMAP.pm +++ b/tests/nginx-tests/nginx-tests/lib/Test/Nginx/IMAP.pm @@ -10,6 +10,7 @@ use warnings; use strict; use Test::More qw//; +use IO::Select; use IO::Socket; use Socket qw/ CRLF /; @@ -21,7 +22,7 @@ sub new { $self->{_socket} = IO::Socket::INET->new( Proto => "tcp", - PeerAddr => "127.0.0.1:8143", + PeerAddr => "127.0.0.1:" . port(8143), @_ ) or die "Can't connect to nginx: $!\n"; @@ -33,6 +34,7 @@ sub new { } $self->{_socket}->autoflush(1); + $self->{_read_buffer} = ''; return $self; } @@ -54,25 +56,39 @@ sub send { $self->{_socket}->print($cmd . CRLF); } -sub read { +sub getline { my ($self) = @_; my $socket = $self->{_socket}; - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - alarm(3); - while (<$socket>) { - log_in($_); - # XXX - next if m/^\d\d\d-/; - last; + + if ($self->{_read_buffer} =~ /^(.*?\x0a)(.*)/ms) { + $self->{_read_buffer} = $2; + return $1; + } + + while (IO::Select->new($socket)->can_read(8)) { + $socket->blocking(0); + my $n = $socket->sysread(my $buf, 1024); + $socket->blocking(1); + last unless $n; + + $self->{_read_buffer} .= $buf; + + if ($self->{_read_buffer} =~ /^(.*?\x0a)(.*)/ms) { + $self->{_read_buffer} = $2; + return $1; } - alarm(0); }; - alarm(0); - if ($@) { - log_in("died: $@"); - return undef; +} + +sub read { + my ($self) = @_; + my $socket = $self->{_socket}; + + while (defined($_ = $self->getline())) { + log_in($_); + last; } + return $_; } @@ -86,12 +102,19 @@ sub ok { Test::More->builder->like($self->read(), qr/^\S+ OK/, @_); } +sub can_read { + my ($self, $timo) = @_; + IO::Select->new($self->{_socket})->can_read($timo || 3); +} + ############################################################################### sub imap_test_daemon { + my ($port) = @_; + my $server = IO::Socket::INET->new( Proto => 'tcp', - LocalAddr => '127.0.0.1:8144', + LocalAddr => '127.0.0.1:' . ($port || port(8144)), Listen => 5, Reuse => 1 ) @@ -102,6 +125,17 @@ sub imap_test_daemon { print $client "* OK fake imap server ready" . CRLF; while (<$client>) { + Test::Nginx::log_core('||', $_); + + while (m/{(\d+)}\x0d?$/) { + print $client '+ ' . CRLF; + $client->sysread(my $buf, $1); + Test::Nginx::log_core('||', $buf); + $buf = <$client>; + Test::Nginx::log_core('||', $buf); + $_ .= $buf; + } + my $tag = ''; $tag = $1 if m/^(\S+)/; diff --git a/tests/nginx-tests/nginx-tests/lib/Test/Nginx/POP3.pm b/tests/nginx-tests/nginx-tests/lib/Test/Nginx/POP3.pm index 9b3660f0a3..1e11a69518 100644 --- a/tests/nginx-tests/nginx-tests/lib/Test/Nginx/POP3.pm +++ b/tests/nginx-tests/nginx-tests/lib/Test/Nginx/POP3.pm @@ -10,6 +10,7 @@ use warnings; use strict; use Test::More qw//; +use IO::Select; use IO::Socket; use Socket qw/ CRLF /; @@ -21,7 +22,7 @@ sub new { $self->{_socket} = IO::Socket::INET->new( Proto => "tcp", - PeerAddr => "127.0.0.1:8110", + PeerAddr => "127.0.0.1:" . port(8110), @_ ) or die "Can't connect to nginx: $!\n"; @@ -33,6 +34,7 @@ sub new { } $self->{_socket}->autoflush(1); + $self->{_read_buffer} = ''; return $self; } @@ -54,25 +56,39 @@ sub send { $self->{_socket}->print($cmd . CRLF); } -sub read { +sub getline { my ($self) = @_; my $socket = $self->{_socket}; - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - alarm(3); - while (<$socket>) { - log_in($_); - # XXX - next if m/^\d\d\d-/; - last; + + if ($self->{_read_buffer} =~ /^(.*?\x0a)(.*)/ms) { + $self->{_read_buffer} = $2; + return $1; + } + + while (IO::Select->new($socket)->can_read(8)) { + $socket->blocking(0); + my $n = $socket->sysread(my $buf, 1024); + $socket->blocking(1); + last unless $n; + + $self->{_read_buffer} .= $buf; + + if ($self->{_read_buffer} =~ /^(.*?\x0a)(.*)/ms) { + $self->{_read_buffer} = $2; + return $1; } - alarm(0); }; - alarm(0); - if ($@) { - log_in("died: $@"); - return undef; +} + +sub read { + my ($self) = @_; + my $socket = $self->{_socket}; + + while (defined($_ = $self->getline())) { + log_in($_); + last; } + return $_; } @@ -86,12 +102,19 @@ sub ok { Test::More->builder->like($self->read(), qr/^\+OK/, @_); } +sub can_read { + my ($self, $timo) = @_; + IO::Select->new($self->{_socket})->can_read($timo || 3); +} + ############################################################################### sub pop3_test_daemon { + my ($port) = @_; + my $server = IO::Socket::INET->new( Proto => 'tcp', - LocalAddr => '127.0.0.1:8111', + LocalAddr => '127.0.0.1:' . ($port || port(8111)), Listen => 5, Reuse => 1 ) diff --git a/tests/nginx-tests/nginx-tests/lib/Test/Nginx/SMTP.pm b/tests/nginx-tests/nginx-tests/lib/Test/Nginx/SMTP.pm index f5ad810f23..c1dd2c7c15 100644 --- a/tests/nginx-tests/nginx-tests/lib/Test/Nginx/SMTP.pm +++ b/tests/nginx-tests/nginx-tests/lib/Test/Nginx/SMTP.pm @@ -10,6 +10,7 @@ use warnings; use strict; use Test::More qw//; +use IO::Select; use IO::Socket; use Socket qw/ CRLF /; @@ -21,7 +22,7 @@ sub new { $self->{_socket} = IO::Socket::INET->new( Proto => "tcp", - PeerAddr => "127.0.0.1:8025", + PeerAddr => "127.0.0.1:" . port(8025), @_ ) or die "Can't connect to nginx: $!\n"; @@ -33,6 +34,7 @@ sub new { } $self->{_socket}->autoflush(1); + $self->{_read_buffer} = ''; return $self; } @@ -54,24 +56,40 @@ sub send { $self->{_socket}->print($cmd . CRLF); } -sub read { +sub getline { my ($self) = @_; my $socket = $self->{_socket}; - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - alarm(3); - while (<$socket>) { - log_in($_); - next if m/^\d\d\d-/; - last; + + if ($self->{_read_buffer} =~ /^(.*?\x0a)(.*)/ms) { + $self->{_read_buffer} = $2; + return $1; + } + + while (IO::Select->new($socket)->can_read(8)) { + $socket->blocking(0); + my $n = $socket->sysread(my $buf, 1024); + $socket->blocking(1); + last unless $n; + + $self->{_read_buffer} .= $buf; + + if ($self->{_read_buffer} =~ /^(.*?\x0a)(.*)/ms) { + $self->{_read_buffer} = $2; + return $1; } - alarm(0); }; - alarm(0); - if ($@) { - log_in("died: $@"); - return undef; +} + +sub read { + my ($self) = @_; + my $socket = $self->{_socket}; + + while (defined($_ = $self->getline())) { + log_in($_); + next if m/^\d\d\d-/; + last; } + return $_; } @@ -90,12 +108,20 @@ sub authok { Test::More->builder->like($self->read(), qr/^235 /, @_); } +sub can_read { + my ($self, $timo) = @_; + IO::Select->new($self->{_socket})->can_read($timo || 3); +} + ############################################################################### sub smtp_test_daemon { + my ($port) = @_; + my $proxy_protocol; + my $server = IO::Socket::INET->new( Proto => 'tcp', - LocalAddr => '127.0.0.1:8026', + LocalAddr => '127.0.0.1:' . ($port || port(8026)), Listen => 5, Reuse => 1 ) @@ -105,6 +131,8 @@ sub smtp_test_daemon { $client->autoflush(1); print $client "220 fake esmtp server ready" . CRLF; + $proxy_protocol = ''; + while (<$client>) { Test::Nginx::log_core('||', $_); @@ -114,6 +142,8 @@ sub smtp_test_daemon { print $client '250 hello ok' . CRLF; } elsif (/^rset/i) { print $client '250 rset ok' . CRLF; + } elsif (/^auth plain/i) { + print $client '235 auth ok' . CRLF; } elsif (/^mail from:[^@]+$/i) { print $client '500 mail from error' . CRLF; } elsif (/^mail from:/i) { @@ -124,6 +154,10 @@ sub smtp_test_daemon { print $client '250 rcpt to ok' . CRLF; } elsif (/^xclient/i) { print $client '220 xclient ok' . CRLF; + } elsif (/^proxy/i) { + $proxy_protocol = $_; + } elsif (/^xproxy/i) { + print $client '211 ' . $proxy_protocol . CRLF; } else { print $client "500 unknown command" . CRLF; } diff --git a/tests/nginx-tests/nginx-tests/lib/Test/Nginx/Stream.pm b/tests/nginx-tests/nginx-tests/lib/Test/Nginx/Stream.pm index b05486bc31..b5cb81a548 100644 --- a/tests/nginx-tests/nginx-tests/lib/Test/Nginx/Stream.pm +++ b/tests/nginx-tests/nginx-tests/lib/Test/Nginx/Stream.pm @@ -84,7 +84,7 @@ sub read { $s = $self->{_socket}; $s->blocking(0); - if (IO::Select->new($s)->can_read($extra{read_timeout} || 5)) { + if (IO::Select->new($s)->can_read($extra{read_timeout} || 8)) { $s->sysread($buf, 1024); }; diff --git a/tests/nginx-tests/nginx-tests/limit_conn_dry_run.t b/tests/nginx-tests/nginx-tests/limit_conn_dry_run.t new file mode 100644 index 0000000000..3a31dea5bc --- /dev/null +++ b/tests/nginx-tests/nginx-tests/limit_conn_dry_run.t @@ -0,0 +1,95 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for limit_conn_dry_run directive, limit_conn_status variable. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy limit_conn limit_req/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + limit_req_zone $binary_remote_addr zone=req:1m rate=30r/m; + limit_conn_zone $binary_remote_addr zone=zone:1m; + + log_format test $uri:$limit_conn_status; + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location /w { + limit_req zone=req burst=10; + } + } + + server { + listen 127.0.0.1:8080; + server_name localhost; + + add_header X-Status $limit_conn_status always; + access_log %%TESTDIR%%/test.log test; + + location /reject { + proxy_pass http://127.0.0.1:8081/w; + limit_conn zone 1; + } + + location /dry { + limit_conn zone 1; + limit_conn_dry_run on; + } + + location / { } + } +} + +EOF + +$t->write_file('w', ''); +$t->run()->plan(6); + +############################################################################### + +like(http_get('/reject'), qr/ 200 .*PASSED/s, 'passed'); + +my $s = http_get('/reject', start => 1); +like(http_get('/reject'), qr/ 503 .*REJECTED(?!_)/s, 'rejected'); +like(http_get('/dry'), qr/ 404 .*REJECTED_DRY_RUN/s, 'rejected dry run'); +unlike(http_get('/'), qr/X-Status/, 'no limit'); + +close $s; + +$t->stop(); + +like($t->read_file('error.log'), qr/limiting connections, dry/, 'log dry run'); +like($t->read_file('test.log'), qr|^/:-|m, 'log not found'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/limit_rate.t b/tests/nginx-tests/nginx-tests/limit_rate.t new file mode 100644 index 0000000000..245c5e8313 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/limit_rate.t @@ -0,0 +1,114 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for limit_rate and limit_rate_after directives. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +plan(skip_all => 'win32') if $^O eq 'MSWin32'; + +my $t = Test::Nginx->new()->has(qw/http proxy/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + log_format test escape=none $uri:$arg_a$arg_xal:$upstream_response_time; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + proxy_pass http://127.0.0.1:8081; + access_log %%TESTDIR%%/test.log test; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + limit_rate 12k; + limit_rate_after 256; + + location /data { + add_header X-Accel-Redirect $arg_xar; + add_header X-Accel-Limit-Rate $arg_xal; + } + + location /redirect { + limit_rate 0; + alias %%TESTDIR%%/data; + } + + location /var { + alias %%TESTDIR%%/data; + limit_rate $arg_l; + limit_rate_after $arg_a; + } + + location /proxy/ { + proxy_pass http://127.0.0.1:8081/; + } + } +} + +EOF + +$t->write_file('data', 'X' x 30000); +$t->run()->plan(7); + +############################################################################### + +# NB: response time may be 1s less, if timer is scheduled on upper half second + +like(http_get('/data'), qr/^(XXXXXXXXXX){3000}\x0d?\x0a?$/m, 'response body'); +like($t->read_file('test.log'), qr/data::[12]/, 'limit_rate'); + +# /proxy -> /redirect +# before 1.17.0, limit was set once in ngx_http_update_location_config() + +http_get('/proxy/data?xar=/redirect'); +like($t->read_file('test.log'), qr!proxy/data::0!, 'X-Accel-Redirect'); + +# X-Accel-Limit-Rate has higher precedence + +http_get('/proxy/data?xar=/redirect&xal=13000'); +like($t->read_file('test.log'), qr!roxy/data:13000:[12]!, 'X-Accel-Limit-Rate'); + +http_get('/var?l=12k&a=256'); +like($t->read_file('test.log'), qr/var:256:[12]/, 'variable'); + +http_get('/var?l=12k&a=40k'); +like($t->read_file('test.log'), qr/var:40k:0/, 'variable after'); + +http_get('/var'); +like($t->read_file('test.log'), qr/var::0/, 'variables unset'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/limit_req_delay.t b/tests/nginx-tests/nginx-tests/limit_req_delay.t index f8dea7b68e..f3627c76ba 100644 --- a/tests/nginx-tests/nginx-tests/limit_req_delay.t +++ b/tests/nginx-tests/nginx-tests/limit_req_delay.t @@ -22,7 +22,7 @@ use Test::Nginx qw/ :DEFAULT http_end /; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http limit_req/); +my $t = Test::Nginx->new()->has(qw/http limit_req/)->plan(4); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -52,7 +52,7 @@ http { EOF $t->write_file('delay.html', 'XtestX'); -$t->try_run('no limit_req delay')->plan(4); +$t->run(); ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/limit_req_dry_run.t b/tests/nginx-tests/nginx-tests/limit_req_dry_run.t new file mode 100644 index 0000000000..24c5e9b514 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/limit_req_dry_run.t @@ -0,0 +1,92 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for nginx limit_req module, limit_req_dry_run directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http limit_req/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + limit_req_zone $binary_remote_addr zone=one:1m rate=1r/m; + + log_format test $uri:$limit_req_status; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + limit_req_dry_run on; + add_header X-Status $limit_req_status always; + access_log %%TESTDIR%%/test.log test; + + location /delay { + limit_req zone=one burst=2; + } + + location /reject { + limit_req zone=one; + } + + location /reject/off { + limit_req zone=one; + + limit_req_dry_run off; + } + + location / { } + } +} + +EOF + +$t->write_file('delay', 'SEE-THIS'); +$t->write_file('reject', 'SEE-THIS'); +$t->run()->plan(8); + +############################################################################### + +like(http_get('/delay'), qr/ 200 .*PASSED/ms, 'dry run - passed'); +like(http_get('/delay'), qr/ 200 .*DELAYED_DRY_RUN/ms, 'dry run - delayed'); +like(http_get('/reject'), qr/ 200 .*REJECTED_DRY_RUN/ms, 'dry run - rejected'); + +like(http_get('/reject/off'), qr/ 503 .*REJECTED/ms, 'dry run off - rejected'); + +unlike(http_get('/'), qr/X-Status/, 'no limit'); + +$t->stop(); + +like($t->read_file('error.log'), qr/delaying request, dry/, 'log - delay'); +like($t->read_file('error.log'), qr/limiting requests, dry/, 'log - reject'); + +like($t->read_file('test.log'), qr|^/:-|m, 'log - not found'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/mail_error_log.t b/tests/nginx-tests/nginx-tests/mail_error_log.t index 013de3ded5..5369d8bcae 100644 --- a/tests/nginx-tests/nginx-tests/mail_error_log.t +++ b/tests/nginx-tests/nginx-tests/mail_error_log.t @@ -13,6 +13,7 @@ use strict; use Test::More; use IO::Select; +use Sys::Hostname; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -43,6 +44,7 @@ events { } mail { + proxy_timeout 15s; auth_http http://127.0.0.1:8080/mail/auth; server { @@ -233,8 +235,7 @@ SKIP: { ok($sec < 60, "$desc valid seconds"); ok(defined($host), "$desc has host"); - chomp(my $hostname = lc `hostname`); - is($host , $hostname, "$desc valid host"); + is($host, lc(hostname()), "$desc valid host"); ok(defined($tag), "$desc has tag"); like($tag, qr'\w+', "$desc valid tag"); diff --git a/tests/nginx-tests/nginx-tests/mail_imap.t b/tests/nginx-tests/nginx-tests/mail_imap.t index da9e7f80d8..e95b306ef9 100644 --- a/tests/nginx-tests/nginx-tests/mail_imap.t +++ b/tests/nginx-tests/nginx-tests/mail_imap.t @@ -12,6 +12,7 @@ use strict; use Test::More; use MIME::Base64; +use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -38,6 +39,7 @@ events { mail { proxy_pass_error_message on; + proxy_timeout 15s; auth_http http://127.0.0.1:8080/mail/auth; server { @@ -58,12 +60,11 @@ http { set $reply ERROR; set $passw ""; - if ($http_auth_smtp_to ~ example.com) { + set $userpass "$http_auth_user:$http_auth_pass"; + if ($userpass = 'test@example.com:secret') { set $reply OK; } - - set $userpass "$http_auth_user:$http_auth_pass"; - if ($userpass ~ '^test@example.com:secret$') { + if ($userpass = 'te\\"st@example.com:se\\"cret') { set $reply OK; } @@ -74,7 +75,7 @@ http { } set $userpass "$http_auth_method:$http_auth_user:$http_auth_pass"; - if ($userpass ~ '^external:test@example.com:$') { + if ($userpass = 'external:test@example.com:') { set $reply OK; set $passw secret; } @@ -92,16 +93,30 @@ http { EOF $t->run_daemon(\&Test::Nginx::IMAP::imap_test_daemon); -$t->run()->plan(14); +$t->run()->plan(29); $t->waitforsocket('127.0.0.1:' . port(8144)); ############################################################################### +# login + my $s = Test::Nginx::IMAP->new(); $s->ok('greeting'); -# bad auth +$s->send('a01 LOGIN'); +$s->check(qr/^a01 BAD/, 'login without arguments'); + +$s->send('a02 LOGIN test@example.com bad'); +$s->check(qr/^a02 NO/, 'login with bad password'); + +$s->send('a03 LOGIN test@example.com secret'); +$s->ok('login'); + +# auth + +$s = Test::Nginx::IMAP->new(); +$s->read(); $s->send('1 AUTHENTICATE'); $s->check(qr/^\S+ BAD/, 'auth without arguments'); @@ -169,4 +184,90 @@ $s->read(); $s->send('1 AUTHENTICATE EXTERNAL ' . encode_base64('test@example.com', '')); $s->ok('auth external with username'); +# quoted strings + +$s = Test::Nginx::IMAP->new(); +$s->read(); + +$s->send('a01 LOGIN "te\\\\\"st@example.com" "se\\\\\"cret"'); +$s->ok('quoted strings'); + +# literals + +$s = Test::Nginx::IMAP->new(); +$s->read(); + +$s->send('a01 LOGIN {18}'); +$s->check(qr/\+ /, 'login username literal continue'); + +$s->send('te\"st@example.com' . ' {8}'); +$s->check(qr/\+ /, 'login password literal continue'); + +$s->send('se\"cret'); +$s->ok('login literals'); + +# non-synchronizing literals + +$s = Test::Nginx::IMAP->new(); +$s->read(); + +$s->send('a01 LOGIN {18+}' . CRLF + . 'te\"st@example.com' . ' {8+}' . CRLF + . 'se\"cret'); +$s->ok('login non-sync literals'); + +# backslash in quotes and literals + +$s = Test::Nginx::IMAP->new(); +$s->read(); + +$s->send('a01 LOGIN {18+}' . CRLF + . 'te\"st@example.com' . ' "se\\\\\"cret"'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +$s->ok('backslash in literal'); + +} + +# pipelining + +$s = Test::Nginx::IMAP->new(); +$s->read(); + +$s->send('a01 INVALID COMMAND WITH ARGUMENTS' . CRLF + . 'a02 NOOP'); +$s->check(qr/^a01 BAD/, 'pipelined invalid command'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +$s->ok('pipelined noop after invalid command'); + +} + +$s->send('a03 FOOBAR {10+}' . CRLF + . 'test test ' . CRLF + . 'a04 NOOP'); +$s->check(qr/^a03 BAD/, 'invalid with non-sync literal'); +$s->check(qr/^(a04 |$)/, 'literal not command'); + +TODO: { +todo_skip('not yet', 2) unless $t->has_version('1.21.0'); + +# skipped without a fix, since with level-triggered event methods +# this hogs cpu till the connection is closed by the backend server, +# and generates a lot of debug logs + +$s = Test::Nginx::IMAP->new(); +$s->read(); + +$s->send('a01 LOGIN test@example.com secret' . CRLF + . 'a02 LOGOUT'); +$s->ok('pipelined login'); +$s->ok('pipelined logout'); + +} + ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/mail_imap_ssl.t b/tests/nginx-tests/nginx-tests/mail_imap_ssl.t index 2089a7f143..1bf1f6da65 100644 --- a/tests/nginx-tests/nginx-tests/mail_imap_ssl.t +++ b/tests/nginx-tests/nginx-tests/mail_imap_ssl.t @@ -34,7 +34,7 @@ plan(skip_all => 'IO::Socket::SSL too old') if $@; local $SIG{PIPE} = 'IGNORE'; my $t = Test::Nginx->new()->has(qw/mail mail_ssl imap http rewrite/) - ->has_daemon('openssl')->plan(12) + ->has_daemon('openssl')->plan(13) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -46,6 +46,7 @@ events { mail { proxy_pass_error_message on; + proxy_timeout 15s; auth_http http://127.0.0.1:8080/mail/auth; auth_http_pass_client_cert on; @@ -98,6 +99,7 @@ http { '$http_auth_ssl_subject:$http_auth_ssl_issuer:' '$http_auth_ssl_serial:$http_auth_ssl_fingerprint:' '$http_auth_ssl_cert:$http_auth_pass'; + log_format test2 '$http_auth_ssl_cipher:$http_auth_ssl_protocol'; server { listen 127.0.0.1:8080; @@ -105,6 +107,7 @@ http { location = /mail/auth { access_log auth.log test; + access_log auth2.log test2; add_header Auth-Status OK; add_header Auth-Server 127.0.0.1; @@ -208,6 +211,19 @@ $s->ok('trusted cert'); $s->send('1 AUTHENTICATE PLAIN ' . $cred->("s5")); $s->read(); +# Auth-SSL-Protocol and Auth-SSL-Cipher headers + +my ($cipher, $sslversion); + +if ($IO::Socket::SSL::VERSION >= 1.964) { + $s = get_ssl_socket(8143); + $cipher = $s->get_cipher(); + $sslversion = $s->get_sslversion(); + $sslversion =~ s/_/./; +} + +undef $s; + # test auth_http request header fields with access_log $t->stop(); @@ -223,4 +239,46 @@ like($f, qr!^on:SUCCESS:(/?CN=2.example.com):\1:\w+:\w+:[^:]+:s4$!m, like($f, qr!^on:SUCCESS:(/?CN=3.example.com):\1:\w+:\w+:[^:]+:s5$!m, 'log - trusted cert'); +SKIP: { +skip 'IO::Socket::SSL version >= 1.964 required', 1 + if $IO::Socket::SSL::VERSION < 1.964; + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.2'); + +$f = $t->read_file('auth2.log'); +like($f, qr|^$cipher:$sslversion$|m, 'log - cipher sslversion'); + +} + +} + +############################################################################### + +sub get_ssl_socket { + my ($port) = @_; + my $s; + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(8); + $s = IO::Socket::SSL->new( + Proto => 'tcp', + PeerAddr => '127.0.0.1:' . port($port), + SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), + SSL_error_trap => sub { die $_[1] } + ); + alarm(0); + }; + alarm(0); + + if ($@) { + log_in("died: $@"); + return undef; + } + + return $s; +} + ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/mail_max_errors.t b/tests/nginx-tests/nginx-tests/mail_max_errors.t new file mode 100644 index 0000000000..f6f0171473 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/mail_max_errors.t @@ -0,0 +1,128 @@ +#!/usr/bin/perl + +# (C) Maxim Dounin +# (C) Nginx, Inc. + +# Tests for mail max_errors. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::IMAP; +use Test::Nginx::POP3; +use Test::Nginx::SMTP; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +local $SIG{PIPE} = 'IGNORE'; + +my $t = Test::Nginx->new()->has(qw/mail imap pop3 smtp/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +mail { + auth_http http://127.0.0.1:8080; # unused + + max_errors 2; + + server { + listen 127.0.0.1:8143; + protocol imap; + } + + server { + listen 127.0.0.1:8110; + protocol pop3; + } + + server { + listen 127.0.0.1:8025; + protocol smtp; + } +} + +EOF + +$t->try_run('no max_errors')->plan(18); + +############################################################################### + +# imap + +my $s = Test::Nginx::IMAP->new(); +$s->read(); + +$s->send('a01 FOO'); +$s->check(qr/^a01 BAD/, 'imap first error'); +$s->send('a02 BAR'); +$s->check(qr/^a02 BAD/, 'imap second error'); +$s->send('a03 BAZZ'); +$s->check(qr/^$/, 'imap max errors'); + +$s = Test::Nginx::IMAP->new(); +$s->read(); + +$s->send('a01 FOO' . CRLF . 'a02 BAR' . CRLF . 'a03 BAZZ'); +$s->check(qr/^a01 BAD/, 'imap pipelined first error'); +$s->check(qr/^a02 BAD/, 'imap pipelined second error'); +$s->check(qr/^$/, 'imap pipelined max errors'); + +# pop3 + +$s = Test::Nginx::POP3->new(); +$s->read(); + +$s->send('FOO'); +$s->check(qr/^-ERR/, 'pop3 first error'); +$s->send('BAR'); +$s->check(qr/^-ERR/, 'pop3 second error'); +$s->send('BAZZ'); +$s->check(qr/^$/, 'pop3 max errors'); + +$s = Test::Nginx::POP3->new(); +$s->read(); + +$s->send('FOO' . CRLF . 'BAR' . CRLF . 'BAZZ'); +$s->check(qr/^-ERR/, 'pop3 pipelined first error'); +$s->check(qr/^-ERR/, 'pop3 pipelined second error'); +$s->check(qr/^$/, 'pop3 pipelined max errors'); + +# smtp + +$s = Test::Nginx::SMTP->new(); +$s->read(); + +$s->send('FOO'); +$s->check(qr/^5.. /, 'smtp first error'); +$s->send('BAR'); +$s->check(qr/^5.. /, 'smtp second error'); +$s->send('BAZZ'); +$s->check(qr/^$/, 'smtp max errors'); + +$s = Test::Nginx::SMTP->new(); +$s->read(); + +$s->send('FOO' . CRLF . 'BAR' . CRLF . 'BAZZ'); +$s->check(qr/^5.. /, 'smtp pipelined first error'); +$s->check(qr/^5.. /, 'smtp pipelined second error'); +$s->check(qr/^$/, 'smtp pipelined max errors'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/mail_pop3.t b/tests/nginx-tests/nginx-tests/mail_pop3.t index 538acb67c5..db6d3ad57a 100644 --- a/tests/nginx-tests/nginx-tests/mail_pop3.t +++ b/tests/nginx-tests/nginx-tests/mail_pop3.t @@ -12,6 +12,7 @@ use strict; use Test::More; use MIME::Base64; +use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -38,6 +39,7 @@ events { mail { proxy_pass_error_message on; + proxy_timeout 15s; auth_http http://127.0.0.1:8080/mail/auth; server { @@ -58,10 +60,6 @@ http { set $reply ERROR; set $passw ""; - if ($http_auth_smtp_to ~ example.com) { - set $reply OK; - } - set $userpass "$http_auth_user:$http_auth_pass"; if ($userpass ~ '^test@example.com:secret$') { set $reply OK; @@ -92,7 +90,7 @@ http { EOF $t->run_daemon(\&Test::Nginx::POP3::pop3_test_daemon); -$t->run()->plan(20); +$t->run()->plan(28); $t->waitforsocket('127.0.0.1:' . port(8111)); @@ -196,6 +194,51 @@ $s->read(); $s->send('AUTH EXTERNAL ' . encode_base64('test@example.com', '')); $s->ok('auth external with username'); +# pipelining + +$s = Test::Nginx::POP3->new(); +$s->read(); + +$s->send('INVALID COMMAND WITH ARGUMENTS' . CRLF + . 'NOOP'); +$s->check(qr/^-ERR/, 'pipelined invalid command'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +$s->ok('pipelined noop after invalid command'); + +} + +$s->send('USER test@example.com' . CRLF + . 'PASS secret' . CRLF + . 'QUIT'); +$s->ok('pipelined user'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +$s->ok('pipelined pass'); +$s->ok('pipelined quit'); + +} + +$s = Test::Nginx::POP3->new(); +$s->read(); + +$s->send('AUTH LOGIN' . CRLF + . encode_base64('test@example.com', '') . CRLF + . encode_base64('secret', '')); +$s->check(qr/\+ VXNlcm5hbWU6/, 'pipelined auth username challenge'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +$s->check(qr/\+ UGFzc3dvcmQ6/, 'pipelined auth password challenge'); +$s->ok('pipelined auth'); + +} + ############################################################################### sub get_auth_caps { diff --git a/tests/nginx-tests/nginx-tests/mail_proxy_protocol.t b/tests/nginx-tests/nginx-tests/mail_proxy_protocol.t new file mode 100644 index 0000000000..d4f4852dfe --- /dev/null +++ b/tests/nginx-tests/nginx-tests/mail_proxy_protocol.t @@ -0,0 +1,134 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for mail proxy module, PROXY protocol with realip. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use MIME::Base64; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::SMTP; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +local $SIG{PIPE} = 'IGNORE'; + +my $t = Test::Nginx->new()->has(qw/mail smtp http rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +mail { + proxy_pass_error_message on; + proxy_timeout 15s; + proxy_smtp_auth on; + proxy_protocol on; + auth_http http://127.0.0.1:8080/mail/auth; + smtp_auth login plain; + + server { + listen 127.0.0.1:8025 proxy_protocol; + protocol smtp; + + auth_http_header X-Type proxy; + } + + server { + listen 127.0.0.1:8027 proxy_protocol; + protocol smtp; + + set_real_ip_from 127.0.0.1/32; + auth_http_header X-Type realip; + } +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location = /mail/auth { + set $reply ERROR; + set $test $http_x_type:$http_client_ip:$http_proxy_protocol_addr; + + if ($test = proxy:127.0.0.1:192.0.2.1) { + set $reply OK; + } + + if ($test = realip:192.0.2.1:192.0.2.1) { + set $reply OK; + } + + add_header Auth-Status $reply; + add_header Auth-Server 127.0.0.1; + add_header Auth-Port %%PORT_8026%%; + add_header Auth-Wait 1; + return 204; + } + } +} + +EOF + +$t->run_daemon(\&Test::Nginx::SMTP::smtp_test_daemon); +$t->run()->plan(8); + +$t->waitforsocket('127.0.0.1:' . port(8026)); + +############################################################################### + +# connection with PROXY protocol + +my $s = Test::Nginx::SMTP->new(PeerAddr => '127.0.0.1:' . port(8025)); +$s->send('PROXY TCP4 192.0.2.1 192.0.2.2 123 5678'); +$s->check(qr/^220 /, "greeting with proxy_protocol"); + +$s->send('EHLO example.com'); +$s->check(qr/^250 /, "ehlo with proxy_protocol"); + +$s->send('AUTH PLAIN ' . encode_base64("\0test\@example.com\0secret", '')); +$s->authok('auth with proxy_protocol'); + +$s->send('XPROXY'); +$s->check(qr/^211 PROXY TCP4 127.0.0.1 127.0.0.1 \d+ \d+/, + 'proxy protocol to backend'); + +# connection with PROXY protocol and set_realip_from + +$s = Test::Nginx::SMTP->new(PeerAddr => '127.0.0.1:' . port(8027)); + +$s->send('PROXY TCP4 192.0.2.1 192.0.2.2 123 5678'); +$s->check(qr/^220 /, "greeting with proxy_protocol and realip"); + +$s->send('EHLO example.com'); +$s->check(qr/^250 /, "ehlo with proxy_protocol and realip"); + +$s->send('AUTH PLAIN ' . encode_base64("\0test\@example.com\0secret", '')); +$s->authok('auth with proxy_protocol and realip'); + +$s->send('XPROXY'); +$s->check(qr/^211 PROXY TCP4 192.0.2.1 127.0.0.1 \d+ \d+/, + 'proxy_protocol to backend and realip'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/mail_proxy_smtp_auth.t b/tests/nginx-tests/nginx-tests/mail_proxy_smtp_auth.t new file mode 100644 index 0000000000..4eb92b94c9 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/mail_proxy_smtp_auth.t @@ -0,0 +1,148 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for nginx mail proxy module, the proxy_smtp_auth directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use MIME::Base64; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::SMTP; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +local $SIG{PIPE} = 'IGNORE'; + +my $t = Test::Nginx->new()->has(qw/mail smtp http rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +mail { + proxy_pass_error_message on; + proxy_timeout 15s; + proxy_smtp_auth on; + auth_http http://127.0.0.1:8080/mail/auth; + smtp_auth login plain external; + + server { + listen 127.0.0.1:8025; + protocol smtp; + } + + server { + listen 127.0.0.1:8027; + protocol smtp; + xclient off; + } +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location = /mail/auth { + add_header Auth-Status OK; + add_header Auth-Server 127.0.0.1; + add_header Auth-Port %%PORT_8026%%; + add_header Auth-Wait 1; + return 204; + } + } +} + +EOF + +$t->run_daemon(\&Test::Nginx::SMTP::smtp_test_daemon); +$t->run()->plan(7); + +$t->waitforsocket('127.0.0.1:' . port(8026)); + +############################################################################### + +# The following combinations may be sent to backend with proxy_smtp_auth on: +# +# ehlo, xclient, auth +# ehlo, xclient, helo, auth +# ehlo, xclient, ehlo, auth +# helo, auth +# ehlo, auth +# +# Test them in order. + +# ehlo, xclient, auth + +my $s = Test::Nginx::SMTP->new(); +$s->read(); +$s->send('AUTH PLAIN ' . encode_base64("\0test\@example.com\0secret", '')); +$s->authok('ehlo, xclient, auth'); + +# ehlo, xclient, helo, auth + +$s = Test::Nginx::SMTP->new(); +$s->read(); +$s->send('HELO example.com'); +$s->read(); +$s->send('AUTH PLAIN ' . encode_base64("\0test\@example.com\0secret", '')); +$s->authok('ehlo, xclient, helo, auth'); + +# ehlo, xclient, ehlo, auth + +$s = Test::Nginx::SMTP->new(); +$s->read(); +$s->send('EHLO example.com'); +$s->read(); +$s->send('AUTH PLAIN ' . encode_base64("\0test\@example.com\0secret", '')); +$s->authok('ehlo, xclient, ehlo, auth'); + +# helo, auth + +$s = Test::Nginx::SMTP->new(PeerAddr => '127.0.0.1:' . port(8027)); +$s->read(); +$s->send('AUTH PLAIN ' . encode_base64("\0test\@example.com\0secret", '')); +$s->authok('helo, auth'); + +# ehlo, auth + +$s = Test::Nginx::SMTP->new(PeerAddr => '127.0.0.1:' . port(8027)); +$s->read(); +$s->send('EHLO example.com'); +$s->read(); +$s->send('AUTH PLAIN ' . encode_base64("\0test\@example.com\0secret", '')); +$s->authok('ehlo, auth'); + +# Try auth external + +$s = Test::Nginx::SMTP->new(); +$s->read(); +$s->send('EHLO example.com'); +$s->read(); + +$s->send('AUTH EXTERNAL'); +$s->check(qr/^334 VXNlcm5hbWU6/, 'auth external challenge'); +$s->send(encode_base64('test@example.com', '')); +$s->check(qr/^4.. /, 'auth external no password'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/mail_smtp.t b/tests/nginx-tests/nginx-tests/mail_smtp.t index 98065f75c1..c398d49f61 100644 --- a/tests/nginx-tests/nginx-tests/mail_smtp.t +++ b/tests/nginx-tests/nginx-tests/mail_smtp.t @@ -39,6 +39,7 @@ events { mail { proxy_pass_error_message on; + proxy_timeout 15s; auth_http http://127.0.0.1:8080/mail/auth; xclient off; @@ -47,6 +48,13 @@ mail { protocol smtp; smtp_auth login plain none cram-md5 external; } + + server { + listen 127.0.0.1:8027; + protocol smtp; + smtp_auth none; + smtp_client_buffer 128; + } } http { @@ -90,7 +98,7 @@ http { EOF $t->run_daemon(\&Test::Nginx::SMTP::smtp_test_daemon); -$t->run()->plan(30); +$t->run()->plan(41); $t->waitforsocket('127.0.0.1:' . port(8026)); @@ -240,6 +248,55 @@ $s->ok('pipelined mail from'); $s->ok('pipelined rcpt to'); $s->ok('pipelined rset'); +# Pipelining with split command + +$s = Test::Nginx::SMTP->new(); +$s->read(); +$s->send('EHLO example.com'); +$s->read(); + +$s->print('MAIL FROM: SIZE=100' . CRLF + . 'RCPT TO:' . CRLF + . 'RS'); + +$s->ok('split pipelined mail from'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +$s->ok('split pipelined rcpt to'); + +} + +$s->send('ET'); +$s->ok('split pipelined rset'); + +# Pipelining longer than smtp_client_buffer + +$s = Test::Nginx::SMTP->new(PeerAddr => '127.0.0.1:' . port(8027)); +$s->read(); +$s->send('EHLO example.com'); +$s->read(); + +$s->send('MAIL FROM: SIZE=100' . CRLF + . 'RCPT TO:' . CRLF + . 'RCPT TO:' . CRLF + . 'RCPT TO:' . CRLF + . 'RCPT TO:' . CRLF + . 'RSET'); + +TODO: { +todo_skip 'long pipelined - not yet', 6 unless $t->has_version('1.21.0'); + +$s->ok('long pipelined mail from'); +$s->ok('long pipelined rcpt to'); +$s->ok('long pipelined rcpt to 2'); +$s->ok('long pipelined rcpt to 3'); +$s->ok('long pipelined rcpt to 4'); +$s->ok('long pipelined rset'); + +} + # Connection must stay even if error returned to rcpt to command $s = Test::Nginx::SMTP->new(); @@ -262,7 +319,26 @@ $s = Test::Nginx::SMTP->new(); $s->read(); $s->print('HEL'); +select undef, undef, undef, 0.1; $s->send('O example.com'); $s->ok('split command'); +# Invalid command split into many packets + +$s = Test::Nginx::SMTP->new(); +$s->read(); + +$s->print('FOO B'); +select undef, undef, undef, 0.1; +$s->send('AR'); +$s->check(qr/^5.. /, 'invalid split command'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.0'); + +$s->send('HELO example.com'); +$s->ok('good after invalid split command'); + +} + ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/mail_smtp_greeting_delay.t b/tests/nginx-tests/nginx-tests/mail_smtp_greeting_delay.t index 515dcb88ad..605c12a890 100644 --- a/tests/nginx-tests/nginx-tests/mail_smtp_greeting_delay.t +++ b/tests/nginx-tests/nginx-tests/mail_smtp_greeting_delay.t @@ -34,6 +34,7 @@ events { mail { proxy_pass_error_message on; + proxy_timeout 15s; auth_http http://127.0.0.1:8080/mail/auth; xclient off; diff --git a/tests/nginx-tests/nginx-tests/mail_smtp_xclient.t b/tests/nginx-tests/nginx-tests/mail_smtp_xclient.t index 3ee861b851..71e40ecaa2 100644 --- a/tests/nginx-tests/nginx-tests/mail_smtp_xclient.t +++ b/tests/nginx-tests/nginx-tests/mail_smtp_xclient.t @@ -36,6 +36,7 @@ events { mail { proxy_pass_error_message on; + proxy_timeout 15s; auth_http http://127.0.0.1:8080/mail/auth; xclient on; diff --git a/tests/nginx-tests/nginx-tests/mail_ssl.t b/tests/nginx-tests/nginx-tests/mail_ssl.t index 76113d1cca..132038bf82 100644 --- a/tests/nginx-tests/nginx-tests/mail_ssl.t +++ b/tests/nginx-tests/nginx-tests/mail_ssl.t @@ -12,8 +12,6 @@ use strict; use Test::More; -use Socket qw/ :DEFAULT $CRLF /; - BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; @@ -35,8 +33,11 @@ eval { }; plan(skip_all => 'Net::SSLeay not installed') if $@; +eval { exists &Net::SSLeay::P_alpn_selected or die; }; +plan(skip_all => 'Net::SSLeay with OpenSSL ALPN support required') if $@; + my $t = Test::Nginx->new()->has(qw/mail mail_ssl imap pop3 smtp/) - ->has_daemon('openssl')->plan(20); + ->has_daemon('openssl')->plan(22); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -213,6 +214,25 @@ like(Net::SSLeay::dump_peer_certificate($ssl), qr/CN=localhost/, 'CN'); ($s, $ssl) = get_ssl_socket(8148); like(Net::SSLeay::dump_peer_certificate($ssl), qr/CN=inherits/, 'CN inner'); +# alpn + +ok(get_ssl_socket(8148, undef, ['imap']), 'alpn'); + +SKIP: { +$t->{_configure_args} =~ /LibreSSL ([\d\.]+)/; +skip 'LibreSSL too old', 1 if defined $1 and $1 lt '3.4.0'; +$t->{_configure_args} =~ /OpenSSL ([\d\.]+)/; +skip 'OpenSSL too old', 1 if defined $1 and $1 lt '1.1.0'; + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.21.4'); + +ok(!get_ssl_socket(8148, undef, ['unknown']), 'alpn rejected'); + +} + +} + # starttls imap $s = Test::Nginx::IMAP->new(PeerAddr => '127.0.0.1:' . port(8149)); @@ -294,19 +314,14 @@ $s->ok('smtp starttls only'); ############################################################################### sub get_ssl_socket { - my ($port, $ses) = @_; - my $s; - - my $dest_ip = inet_aton('127.0.0.1'); - my $dest_serv_params = sockaddr_in(port($port), $dest_ip); - - socket($s, &AF_INET, &SOCK_STREAM, 0) or die "socket: $!"; - connect($s, $dest_serv_params) or die "connect: $!"; + my ($port, $ses, $alpn) = @_; + my $s = IO::Socket::INET->new('127.0.0.1:' . port($port)); my $ssl = Net::SSLeay::new($ctx) or die("Failed to create SSL $!"); Net::SSLeay::set_session($ssl, $ses) if defined $ses; + Net::SSLeay::set_alpn_protos($ssl, $alpn) if defined $alpn; Net::SSLeay::set_fd($ssl, fileno($s)); - Net::SSLeay::connect($ssl) or die("ssl connect"); + Net::SSLeay::connect($ssl) == 1 or return; return ($s, $ssl); } diff --git a/tests/nginx-tests/nginx-tests/mail_ssl_conf_command.t b/tests/nginx-tests/nginx-tests/mail_ssl_conf_command.t new file mode 100644 index 0000000000..28f28bd288 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/mail_ssl_conf_command.t @@ -0,0 +1,124 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for mail ssl module, ssl_conf_command. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { + require Net::SSLeay; + Net::SSLeay::load_error_strings(); + Net::SSLeay::SSLeay_add_ssl_algorithms(); + Net::SSLeay::randomize(); +}; +plan(skip_all => 'Net::SSLeay not installed') if $@; + +my $t = Test::Nginx->new()->has(qw/mail mail_ssl imap/) + ->has_daemon('openssl'); + +$t->{_configure_args} =~ /OpenSSL ([\d\.]+)/; +plan(skip_all => 'OpenSSL too old') unless defined $1 and $1 ge '1.0.2'; +plan(skip_all => 'no ssl_conf_command') if $t->has_module('BoringSSL'); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +mail { + auth_http http://127.0.0.1:8080; # unused + + server { + listen 127.0.0.1:8443 ssl; + protocol imap; + + ssl_protocols TLSv1.2; + + ssl_session_tickets off; + ssl_conf_command Options SessionTicket; + + ssl_prefer_server_ciphers on; + ssl_conf_command Options -ServerPreference; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256; + + ssl_certificate localhost.crt; + ssl_certificate_key localhost.key; + ssl_conf_command Certificate override.crt; + ssl_conf_command PrivateKey override.key; + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('localhost', 'override') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->run()->plan(3); + +############################################################################### + +my $ctx = Net::SSLeay::CTX_new() or die("Failed to create SSL_CTX $!"); + +my ($s, $ssl) = get_ssl_socket(); +like(Net::SSLeay::dump_peer_certificate($ssl), qr/CN=override/, 'Certificate'); + +my $ses = Net::SSLeay::get_session($ssl); +($s, $ssl) = get_ssl_socket(ses => $ses); +ok(Net::SSLeay::session_reused($ssl), 'SessionTicket'); + +($s, $ssl) = get_ssl_socket(ciphers => + 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384'); +is(Net::SSLeay::get_cipher($ssl), + 'ECDHE-RSA-AES128-GCM-SHA256', 'ServerPreference'); + +############################################################################### + +sub get_ssl_socket { + my (%extra) = @_; + + my $s = IO::Socket::INET->new('127.0.0.1:' . port(8443)); + my $ssl = Net::SSLeay::new($ctx) or die("Failed to create SSL $!"); + Net::SSLeay::set_session($ssl, $extra{ses}) if $extra{ses}; + Net::SSLeay::set_cipher_list($ssl, $extra{ciphers}) if $extra{ciphers}; + Net::SSLeay::set_fd($ssl, fileno($s)); + Net::SSLeay::connect($ssl) or die("ssl connect"); + return ($s, $ssl); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/memcached_fake_extra.t b/tests/nginx-tests/nginx-tests/memcached_fake_extra.t new file mode 100644 index 0000000000..1eb60605aa --- /dev/null +++ b/tests/nginx-tests/nginx-tests/memcached_fake_extra.t @@ -0,0 +1,93 @@ +#!/usr/bin/perl + +# (C) Maxim Dounin +# (C) Nginx, Inc. + +# Test for memcached backend returning extra data after trailer. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite memcached/)->plan(1) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + set $memcached_key $uri; + memcached_pass 127.0.0.1:8081; + } + } +} + +EOF + +$t->run_daemon(\&memcached_fake_daemon); +$t->run(); + +$t->waitforsocket('127.0.0.1:' . port(8081)) + or die "Can't start fake memcached"; + +############################################################################### + +like(http_get('/'), qr/SEE-THIS/, 'memcached data after trailer'); + +############################################################################### + +sub memcached_fake_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . port(8081), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + while (<$client>) { + last if (/\x0d\x0a$/); + } + + print $client 'VALUE / 0 8' . CRLF; + print $client 'SEE-THIS' . CRLF . 'END' . CRLF + . "\0" . ("1" x 1024); + + select(undef, undef, undef, 0.2); + + print $client 'EXTRA' . CRLF; + close $client; + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/merge_slashes.t b/tests/nginx-tests/nginx-tests/merge_slashes.t new file mode 100644 index 0000000000..7407cabe19 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/merge_slashes.t @@ -0,0 +1,58 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for URI normalization, merge_slashes off. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite/)->plan(2) + ->write_file_expand('nginx.conf', <<'EOF')->run(); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + merge_slashes off; + + location / { + add_header X-URI "x $uri x"; + return 204; + } + } +} + +EOF + +############################################################################### + +like(http_get('/foo//../bar'), qr!x /foo/bar x!, 'merge slashes'); +like(http_get('/foo///../bar'), qr!x /foo//bar x!, 'merge slashes 2'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/mirror.t b/tests/nginx-tests/nginx-tests/mirror.t new file mode 100644 index 0000000000..195734fa61 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/mirror.t @@ -0,0 +1,86 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http mirror module. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http mirror/)->plan(8); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + log_format test $uri; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + mirror /mirror; + + location /off { + mirror off; + } + } + + location /many { + mirror /mirror/1; + mirror /mirror/2; + } + + location /mirror { + log_subrequest on; + access_log test$args.log test; + } + } +} + +EOF + +$t->write_file('index.html', ''); +$t->write_file('many', ''); +$t->write_file('off', ''); +$t->run(); + +############################################################################### + +like(http_get('/index.html?1'), qr/200 OK/, 'request'); +like(http_get('/?2'), qr/200 OK/, 'internal redirect'); +like(http_get('/off?3'), qr/200 OK/, 'mirror off'); +like(http_get('/many?4'), qr/200 OK/, 'mirror many'); + +$t->stop(); + +is($t->read_file('test1.log'), "/mirror\n", 'log - request'); +is($t->read_file('test2.log'), "/mirror\n/mirror\n", 'log - redirect'); +ok(!-e $t->testdir() . '/test3.log', 'log - mirror off'); +is($t->read_file('test4.log'), "/mirror/1\n/mirror/2\n", 'log - mirror many'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/mirror_proxy.t b/tests/nginx-tests/nginx-tests/mirror_proxy.t new file mode 100644 index 0000000000..f760d9dbba --- /dev/null +++ b/tests/nginx-tests/nginx-tests/mirror_proxy.t @@ -0,0 +1,140 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http mirror module and it's interaction with proxy. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use IO::Select; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy mirror rewrite limit_req/); + +$t->write_file_expand('nginx.conf', <<'EOF')->plan(7); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + limit_req_zone $uri zone=slow:1m rate=30r/m; + log_format test $request_uri:$request_body; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + mirror /mirror; + proxy_pass http://127.0.0.1:8081; + } + + location /off { + mirror /mirror/off; + mirror_request_body off; + proxy_pass http://127.0.0.1:8081; + } + + location /mirror { + internal; + proxy_pass http://127.0.0.1:8082; + limit_req zone=slow burst=1; + } + + location /mirror/off { + internal; + proxy_pass http://127.0.0.1:8082; + proxy_set_header Content-Length ""; + } + } + + server { + listen 127.0.0.1:8081; + listen 127.0.0.1:8082; + server_name localhost; + + location / { + proxy_pass http://127.0.0.1:$server_port/return204; + access_log %%TESTDIR%%/test.log test; + add_header X-Body $request_body; + } + + location /return204 { + return 204; + } + } +} + +EOF + +$t->run(); + +############################################################################### + +like(http_post('/'), qr/X-Body: 1234567890\x0d?$/m, 'mirror proxy'); +like(http_post('/off'), qr/X-Body: 1234567890\x0d?$/m, 'mirror_request_body'); + +# delayed subrequest should not affect main request processing nor stuck itself + +my $s = http_post('/delay?1', start => 1); +like(read_keepalive($s), qr/X-Body: 1234567890\x0d?$/m, 'mirror delay'); + +$t->todo_alerts(); +$t->stop(); + +my $log = $t->read_file('test.log'); +like($log, qr!^/:1234567890$!m, 'log - request body'); +like($log, qr!^/mirror:1234567890$!m, 'log - request body in mirror'); +like($log, qr!^/off:1234567890$!m, 'log - mirror_request_body off'); +like($log, qr!^/mirror/off:-$!m,, 'log - mirror_request_body off in mirror'); + +############################################################################### + +sub http_post { + my ($url, %extra) = @_; + + http(<new($s)->can_read(3)) { + sysread($s, my $buffer, 4096) or last; + $data .= $buffer; + last if $data =~ /^\x0d\x0a/ms; + } + + log_in($data); + return $data; +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/mp4.t b/tests/nginx-tests/nginx-tests/mp4.t index c492198106..88ca1e9c7e 100644 --- a/tests/nginx-tests/nginx-tests/mp4.t +++ b/tests/nginx-tests/nginx-tests/mp4.t @@ -13,12 +13,10 @@ use strict; use Test::More; -use Config; - BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; -use Test::Nginx; +use Test::Nginx qw/ :DEFAULT http_content /; ############################################################################### @@ -67,7 +65,23 @@ system('ffmpeg -nostdin -loglevel quiet -y ' . "${\($t->testdir())}/no_mdat.mp4") == 0 or die "Can't create mp4 file: $!"; -$t->run()->plan(26); +my $sbad = <<'EOF'; +00000000: 00 00 00 1c 66 74 79 70 69 73 6f 6d 00 00 02 00 |....ftypisom....| +00000010: 69 73 6f 6d 69 73 6f 32 6d 70 34 31 00 00 00 09 |isomiso2mp41....| +00000020: 6d 64 61 74 00 00 00 00 94 6d 6f 6f 76 00 00 00 |mdat.....moov...| +00000030: 8c 74 72 61 6b 00 00 00 84 6d 64 69 61 00 00 00 |.trak....mdia...| +00000040: 7c 6d 69 6e 66 00 00 00 74 73 74 62 6c 00 00 00 ||minf...tstbl...| +00000050: 18 73 74 74 73 00 00 00 00 00 00 00 01 00 00 03 |.stts...........| +00000060: 3a 00 00 04 00 00 00 00 28 73 74 73 63 00 00 00 |:.......(stsc...| +00000070: 00 00 00 00 02 00 00 00 01 00 00 03 0f 00 00 00 |................| +00000080: 01 00 00 00 02 00 00 00 2b 00 00 00 01 00 00 00 |........+.......| +00000090: 14 73 74 73 7a 00 00 00 00 00 00 05 a9 00 00 03 |.stsz...........| +000000a0: 3b 00 00 00 18 63 6f 36 34 00 00 00 00 00 00 00 |;....co64.......| +000000b0: 01 ff ff ff ff f0 0f fb e7 |.........| +EOF + +$t->write_file('bad.mp4', unhex($sbad)); +$t->run()->plan(27); ############################################################################### @@ -101,6 +115,10 @@ like(http_head("$test_uri?start=21"), qr!HTTP/1.1 500!, 'start beyond EOF'); $test_uri = '/no_mdat.mp4', goto again unless $test_uri eq '/no_mdat.mp4'; +# corrupted formats + +like(http_get("/bad.mp4?start=0.5"), qr/500 Internal/, 'co64 chunk beyond EOF'); + ############################################################################### sub durations { @@ -118,11 +136,24 @@ sub durations { $uri .= "?end=$end"; } - $t->write_file('frag.mp4', Test::Nginx::http_content(http_get($uri))); + $t->write_file('frag.mp4', http_content(http_get($uri))); my $r = `ffprobe -show_streams $path 2>/dev/null`; Test::Nginx::log_core('||', $r); sprintf "%.1f %.1f", $r =~ /duration=(\d+\.\d+)/g; } +sub unhex { + my ($input) = @_; + my $buffer = ''; + + for my $l ($input =~ m/: +((?:[0-9a-f]{2,4} +)+) /gms) { + for my $v ($l =~ m/[0-9a-f]{2}/g) { + $buffer .= chr(hex($v)); + } + } + + return $buffer; +} + ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/mp4_start_key_frame.t b/tests/nginx-tests/nginx-tests/mp4_start_key_frame.t new file mode 100644 index 0000000000..0d530fbbaa --- /dev/null +++ b/tests/nginx-tests/nginx-tests/mp4_start_key_frame.t @@ -0,0 +1,104 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for the mp4_start_key_frame directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx qw/ :DEFAULT http_content /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http mp4/)->has_daemon('ffprobe') + ->has_daemon('ffmpeg') + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + mp4; + } + + location /force/ { + mp4; + mp4_start_key_frame on; + alias %%TESTDIR%%/; + } + } +} + +EOF + +plan(skip_all => 'no lavfi') + unless grep /lavfi/, `ffmpeg -loglevel quiet -formats`; +system('ffmpeg -nostdin -loglevel quiet -y ' + . '-f lavfi -i testsrc=duration=10:size=320x200:rate=15 ' + . '-pix_fmt yuv420p -g 15 -c:v libx264 ' + . "${\($t->testdir())}/test.mp4") == 0 + or die "Can't create mp4 file: $!"; +$t->try_run('no mp4_start_key_frame')->plan(4); + +############################################################################### + +# baseline durations + +my $test_uri = '/test.mp4'; +is(durations($t, 2.0, 4.0), '2.00', 'start at key frame'); +isnt(durations($t, 2.1, 4.0), '1.90', 'start off key frame'); + +# with forced start at key frame + +$test_uri = '/force/test.mp4'; +is(durations($t, 2.0, 4.0), '2.00', 'start at key frame force'); +is(durations($t, 2.1, 4.0), '1.90', 'start off key frame force'); + +############################################################################### + +sub durations { + my ($t, $start, $end) = @_; + my $path = $t->{_testdir} . '/frag.mp4'; + + my $uri = $test_uri; + if (defined $start) { + $uri .= "?start=$start"; + if (defined $end) { + $uri .= "&end=$end"; + } + + } elsif (defined $end) { + $uri .= "?end=$end"; + } + + $t->write_file('frag.mp4', http_content(http_get($uri))); + + my $r = `ffprobe -show_streams $path 2>/dev/null`; + Test::Nginx::log_core('||', $r); + sprintf "%.2f", $r =~ /duration=(\d+\.\d+)/g; +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/msie_refresh.t b/tests/nginx-tests/nginx-tests/msie_refresh.t new file mode 100644 index 0000000000..374f8b2646 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/msie_refresh.t @@ -0,0 +1,96 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Test for msie_refresh. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite ssi/)->plan(5) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + msie_refresh on; + + location / { + return 301 text; + } + + location /space { + return 301 "space "; + } + + location /error_page { + return 301; + error_page 301 text; + } + + location /off { + msie_refresh off; + return 301 text; + } + + location /ssi { + ssi on; + } + } +} + +EOF + +$t->write_file('ssi.html', 'XX'); +$t->run(); + +############################################################################### + +like(get('/'), qr/Refresh.*URL=text"/, 'msie refresh'); +like(get('/space'), qr/URL=space%20"/, 'msie refresh escaped url'); +like(get('/error_page'), qr/URL=text"/, 'msie refresh error page'); + +unlike(get('/off'), qr/Refresh/, 'msie refresh disabled'); + +unlike(get('/ssi.html'), qr/^0\x0d\x0a?\x0d\x0a?\w/m, 'only final chunk'); + +############################################################################### + +sub get { + my ($url, $extra) = @_; + return http(<new()->has(qw/http proxy cache/)->plan(1) +my $t = Test::Nginx->new()->has(qw/http proxy cache/)->plan(2) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -46,6 +46,7 @@ http { proxy_pass http://127.0.0.1:8081; proxy_cache cache; proxy_cache_lock on; + proxy_cache_valid 1h; } location /error412 { @@ -76,6 +77,15 @@ like(http_match_get('/t.html'), qr//, 'request 412'); $t->todo_alerts(); +# in addition, in 1.11.10 .. 1.17.1, if the response was previously +# cached, such a request resulted in r->cache null pointer dereference +# in ngx_http_upstream_cache_background_update(), after it was reset +# during internal redirect + +http_get('/t.html'); + +like(http_match_get('/t.html'), qr//, 'request 412 cached'); + ############################################################################### sub http_match_get { diff --git a/tests/nginx-tests/nginx-tests/perl.t b/tests/nginx-tests/nginx-tests/perl.t index f8e7abedd2..c524c07f8e 100644 --- a/tests/nginx-tests/nginx-tests/perl.t +++ b/tests/nginx-tests/nginx-tests/perl.t @@ -23,7 +23,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http perl rewrite/)->plan(17) +my $t = Test::Nginx->new()->has(qw/http perl rewrite/)->plan(27) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -48,6 +48,8 @@ http { my $r = shift; + $r->status(204) if $r->args =~ /204/; + $r->send_http_header("text/plain"); return OK if $r->header_only; @@ -60,6 +62,7 @@ http { $r->print("xfoo: ", $r->header_in("X-Foo"), "\n"); $r->print("cookie: ", $r->header_in("Cookie"), "\n"); $r->print("xff: ", $r->header_in("X-Forwarded-For"), "\n"); + $r->print("connection: ", $r->header_in("Connection"), "\n"); return OK; }'; @@ -105,6 +108,25 @@ http { } }'; } + + location /discard { + perl 'sub { + use warnings; + use strict; + + my $r = shift; + + $r->discard_request_body; + + $r->send_http_header("text/plain"); + + return OK if $r->header_only; + + $r->print("host: ", $r->header_in("Host"), "\n"); + + return OK; + }'; + } } } @@ -114,7 +136,9 @@ $t->run(); ############################################################################### -like(http_get('/'), qr/TEST/, 'perl response'); +like(http_get('/'), qr/ 200 .*TEST/s, 'perl response'); +like(http_head('/'), qr/ 200 (?!.*TEST)/s, 'perl header_only'); +like(http_get('/?204'), qr/ 204 (?!.*TEST)/s, 'perl status, args'); # various $r->header_in() cases @@ -129,6 +153,18 @@ like(http( . 'Host: localhost' . CRLF . CRLF ), qr/xfoo: foo/, 'perl header_in unknown'); +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like(http( + 'GET / HTTP/1.0' . CRLF + . 'X-Foo: foo' . CRLF + . 'X-Foo: bar' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/xfoo: foo, bar/, 'perl header_in unknown2'); + +} + like(http( 'GET / HTTP/1.0' . CRLF . 'Cookie: foo' . CRLF @@ -155,6 +191,24 @@ like(http( . 'Host: localhost' . CRLF . CRLF ), qr/xff: foo1, foo2/, 'perl header_in xff2'); +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like(http( + 'GET / HTTP/1.0' . CRLF + . 'Connection: close' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/connection: close/, 'perl header_in connection'); + +like(http( + 'GET / HTTP/1.0' . CRLF + . 'Connection: close' . CRLF + . 'Connection: foo' . CRLF + . 'Host: localhost' . CRLF . CRLF +), qr/connection: close, foo/, 'perl header_in connection2'); + +} + # headers_out content-length tests with range filter like(http_get('/range'), qr/Content-Length: 42.*^x{42}$/ms, @@ -173,19 +227,22 @@ like(http( ), qr/Content-Length: (?!42).*^xx\x0d.*^xxx\x0d/ms, 'perl header_out content-length multipart'); -TODO: { -local $TODO = 'not yet'; - like(http( 'GET /range HTTP/1.0' . CRLF . 'Host: localhost' . CRLF . 'Range: bytes=100000-' . CRLF . CRLF ), qr|^\QHTTP/1.1 416\E.*(?!xxx)|ms, 'perl range not satisfiable'); -} +like(http( + 'GET / HTTP/1.0' . CRLF + . 'Host: localhost' . CRLF + . 'If-Match: tt' . CRLF . CRLF +), qr|200 OK|ms, 'perl precondition failed'); # various request body tests +like(http_get('/body'), qr/400 Bad Request/, 'perl no body'); + like(http( 'GET /body HTTP/1.0' . CRLF . 'Host: localhost' . CRLF @@ -240,4 +297,34 @@ like(http( body => '67890' . CRLF . '0' . CRLF . CRLF ), qr/body: 1234567890/, 'perl body chunked split'); +like(http( + 'GET /discard HTTP/1.1' . CRLF + . 'Host: localhost' . CRLF + . 'Connection: close' . CRLF + . 'Transfer-Encoding: chunked' . CRLF . CRLF + . 'a' . CRLF + . '1234567890' . CRLF + . '0' . CRLF . CRLF +), qr/host: localhost/, 'perl body discard'); + +like(http( + 'GET /discard HTTP/1.1' . CRLF + . 'Host: localhost' . CRLF + . 'Connection: close' . CRLF + . 'Transfer-Encoding: chunked' . CRLF . CRLF + . 'ak' . CRLF + . '1234567890' . CRLF + . '0' . CRLF . CRLF +), qr/400 Bad Request/, 'perl body discard bad chunk'); + +like(http( + 'GET /body HTTP/1.1' . CRLF + . 'Host: localhost' . CRLF + . 'Connection: close' . CRLF + . 'Transfer-Encoding: chunked' . CRLF . CRLF + . 'ak' . CRLF + . '1234567890' . CRLF + . '0' . CRLF . CRLF +), qr/400 Bad Request/, 'perl body bad chunk'); + ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy.t b/tests/nginx-tests/nginx-tests/proxy.t index fd661f881c..324e451d4b 100644 --- a/tests/nginx-tests/nginx-tests/proxy.t +++ b/tests/nginx-tests/nginx-tests/proxy.t @@ -11,8 +11,6 @@ use strict; use Test::More; -use Socket; - BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; @@ -23,7 +21,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(30); +my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(28); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -59,13 +57,13 @@ http { location / { proxy_pass http://127.0.0.1:8081; - proxy_read_timeout 1s; + proxy_read_timeout 2s; proxy_connect_timeout 2s; } location /var { proxy_pass http://$arg_b; - proxy_read_timeout 1s; + proxy_read_timeout 2s; proxy_connect_timeout 2s; } @@ -139,26 +137,16 @@ close ($s); $re = qr/(\d\.\d{3}|-)/; ($ct, $ct2, $ht, $ht2, $rt, $rt2) = get('/pnu', many => 1); + cmp_ok($ct, '<', 1, 'connect time - next'); cmp_ok($ct2, '<', 1, 'connect time - next 2'); -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.15.7'); - is($ht, '-', 'header time - next'); - -} - cmp_ok($ht2, '<', 1, 'header time - next 2'); -cmp_ok($rt, '>=', 1, 'response time - next'); - -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.15.7'); +cmp_ok($rt, '>=', 1, 'response time - next'); is($rt2, '-', 'response time - next 2'); -} - $t->stop(); ($ct, $ht, $rt, $ct2, $ht2, $rt2, $ct3, $ht3, $rt3) @@ -166,15 +154,6 @@ $t->stop(); cmp_ok($ct, '<', 1, 'connect time log - slow response header'); cmp_ok($ct2, '<', 1, 'connect time log - slow response body'); - -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.15.7'); - -isnt($ct3, '-', 'connect time log - client close set'); - -} - -$ct3 = 0 if $ct3 eq '-'; cmp_ok($ct3, '<', 1, 'connect time log - client close'); cmp_ok($ht, '>=', 1, 'header time log - slow response header'); @@ -183,16 +162,8 @@ is($ht3, '-', 'header time log - client close'); cmp_ok($rt, '>=', 1, 'response time log - slow response header'); cmp_ok($rt2, '>=', 1, 'response time log - slow response body'); - -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.15.7'); - -isnt($rt3, '-', 'response time log - client close set'); -$rt3 = 0 if $rt3 eq '-'; cmp_ok($rt3, '>', $ct3, 'response time log - client close'); -} - ############################################################################### sub get { diff --git a/tests/nginx-tests/nginx-tests/proxy_available.t b/tests/nginx-tests/nginx-tests/proxy_available.t new file mode 100644 index 0000000000..6a5b667d78 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/proxy_available.t @@ -0,0 +1,137 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http proxy module with available bytes counting. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use IO::Select; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx qw/ :DEFAULT http_end /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(2); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /buffered { + proxy_pass http://127.0.0.1:8081; + proxy_buffer_size 512; + } + + location /unbuffered { + proxy_pass http://127.0.0.1:8082; + proxy_buffer_size 512; + proxy_buffering off; + } + } +} + +EOF + +$t->run_daemon(\&http_daemon, port(8081)); +$t->run_daemon(\&http_daemon, port(8082)); +$t->run(); + +$t->waitforsocket('127.0.0.1:' . port(8081)); +$t->waitforsocket('127.0.0.1:' . port(8082)); + +############################################################################### + +# ticket #2367: socket leaks with EPOLLRDHUP +# due to missing rev->ready reset on rev->available == 0 +# +# to reproduce leaks, the first part of the response should fit proxy buffer + +my $s = http_get('/buffered', start => 1); +IO::Select->new($s)->can_read(3); + +$t->reload(); + +TODO: { +local $TODO = 'not yet' if $^O eq 'linux' and !$t->has_version('1.23.1'); + +like(http_end($s), qr/AND-THIS/, 'zero available - buffered'); + +} + +$s = http_get('/unbuffered', start => 1); +IO::Select->new($s)->can_read(3); + +$t->stop(); + +like(http_end($s), qr/AND-THIS/, 'zero available - unbuffered'); + +$t->todo_alerts() if $^O eq 'linux' and !$t->has_version('1.23.1'); + +############################################################################### + +sub http_daemon { + my ($port) = @_; + + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => "127.0.0.1:$port", + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + my $headers = ''; + my $uri = ''; + + while (<$client>) { + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + next if $headers eq ''; + + my $r = <new()->has(qw/http proxy cache rewrite/)->plan(19); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + proxy_cache_path %%TESTDIR%%/cache keys_zone=NAME:1m; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + add_header X-Cache-Status $upstream_cache_status; + + location / { + proxy_pass http://127.0.0.1:8081; + proxy_cache NAME; + + proxy_cache_background_update on; + } + + location /ignore { + proxy_pass http://127.0.0.1:8081; + proxy_cache NAME; + + proxy_ignore_headers Cache-Control Expires; + proxy_ignore_headers X-Accel-Expires; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location /expires { + add_header Expires "Thu, 31 Dec 2037 23:55:55 GMT"; + return 204; + } + + location /cache-control { + add_header Cache-Control max-age=60; + return 204; + } + + location /x-accel-expires { + add_header X-Accel-Expires 60; + return 204; + } + + location /x-accel-expires-at { + add_header X-Accel-Expires @60; + return 204; + } + + location /x-accel-expires-duplicate { + add_header X-Accel-Expires 60; + add_header X-Accel-Expires 0; + return 204; + } + + location /ignore { + add_header Expires "Thu, 31 Dec 2037 23:55:55 GMT"; + add_header Cache-Control max-age=60; + add_header X-Accel-Expires 60; + return 204; + } + + location /cache-control-before-expires { + add_header Cache-Control max-age=60; + add_header Expires "Thu, 01 Jan 1970 00:00:01 GMT"; + return 204; + } + + location /cache-control-after-expires { + add_header Expires "Thu, 01 Jan 1970 00:00:01 GMT"; + add_header Cache-Control max-age=60; + return 204; + } + + location /cache-control-no-cache-before-expires { + add_header Cache-Control no-cache; + add_header Expires "Thu, 31 Dec 2037 23:55:55 GMT"; + return 204; + } + + location /cache-control-no-cache-after-expires { + add_header Expires "Thu, 31 Dec 2037 23:55:55 GMT"; + add_header Cache-Control no-cache; + return 204; + } + + location /x-accel-expires-before { + add_header X-Accel-Expires 60; + add_header Expires "Thu, 01 Jan 1970 00:00:01 GMT"; + add_header Cache-Control no-cache; + return 204; + } + + location /x-accel-expires-after { + add_header Expires "Thu, 01 Jan 1970 00:00:01 GMT"; + add_header Cache-Control no-cache; + add_header X-Accel-Expires 60; + return 204; + } + + location /x-accel-expires-0-before { + add_header X-Accel-Expires 0; + add_header Cache-Control max-age=60; + add_header Expires "Thu, 31 Dec 2037 23:55:55 GMT"; + return 204; + } + + location /x-accel-expires-0-after { + add_header Cache-Control max-age=60; + add_header Expires "Thu, 31 Dec 2037 23:55:55 GMT"; + add_header X-Accel-Expires 0; + return 204; + } + + location /cache-control-no-cache-one { + add_header Cache-Control "no-cache, max-age=60"; + return 204; + } + + location /cache-control-no-cache-multi { + add_header Cache-Control no-cache; + add_header Cache-Control max-age=60; + return 204; + } + + location /extension-before-x-accel-expires { + add_header Cache-Control stale-while-revalidate=2145902155; + add_header X-Accel-Expires @1; + return 204; + } + + location /extension-after-x-accel-expires { + add_header X-Accel-Expires @1; + add_header Cache-Control stale-while-revalidate=2145902155; + return 204; + } + + location /set-cookie { + add_header Set-Cookie foo; + add_header Expires "Thu, 01 Jan 1970 00:00:01 GMT"; + add_header Cache-control max-age=60; + return 204; + } + } +} + +EOF + +$t->run(); + +############################################################################### + +# cache headers work + +like(get('/expires'), qr/HIT/, 'expires'); +like(get('/cache-control'), qr/HIT/, 'cache-control'); +like(get('/x-accel-expires'), qr/HIT/, 'x-accel-expires'); +like(get('/x-accel-expires-at'), qr/EXPIRED/, 'x-accel-expires at'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +# the second header to disable cache is duplicate and ignored + +like(get('/x-accel-expires-duplicate'), qr/HIT/, 'x-accel-expires duplicate'); + +} + +# with cache headers ignored, the response will be fresh + +like(get('/ignore'), qr/MISS/, 'cache headers ignored'); + +# Cache-Control is preferred over Expires + +like(get('/cache-control-before-expires'), qr/HIT/, + 'cache-control before expires'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like(get('/cache-control-after-expires'), qr/HIT/, + 'cache-control after expires'); + +} + +like(get('/cache-control-no-cache-before-expires'), qr/MISS/, + 'cache-control no-cache before expires'); +like(get('/cache-control-no-cache-after-expires'), qr/MISS/, + 'cache-control no-cache after expires'); + +# X-Accel-Expires is preferred over both Cache-Control and Expires + +like(get('/x-accel-expires-before'), qr/HIT/, 'x-accel-expires before'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like(get('/x-accel-expires-after'), qr/HIT/, 'x-accel-expires after'); + +} + +like(get('/x-accel-expires-0-before'), qr/MISS/, 'x-accel-expires 0 before'); +like(get('/x-accel-expires-0-after'), qr/MISS/, 'x-accel-expires 0 after'); + +# "Cache-Control: no-cache" disables caching, no matter of "max-age" + +like(get('/cache-control-no-cache-one'), qr/MISS/, + 'cache-control no-cache'); +like(get('/cache-control-no-cache-multi'), qr/MISS/, + 'cache-control no-cache multi line'); + +# Cache-Control extensions are preserved with X-Accel-Expires + +like(get('/extension-before-x-accel-expires'), + qr/STALE/, 'cache-control extensions before x-accel-expires'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like(get('/extension-after-x-accel-expires'), + qr/STALE/, 'cache-control extensions after x-accel-expires'); + +} + +# Set-Cookie is considered when caching with Cache-Control + +like(get('/set-cookie'), qr/MISS/, 'set-cookie not cached'); + +############################################################################### + +sub get { + my ($uri) = @_; + http_get($uri); + http_get($uri); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_cache_error.t b/tests/nginx-tests/nginx-tests/proxy_cache_error.t index 2ac2d7e614..d9c183e7e0 100644 --- a/tests/nginx-tests/nginx-tests/proxy_cache_error.t +++ b/tests/nginx-tests/nginx-tests/proxy_cache_error.t @@ -78,8 +78,8 @@ like(http_head('/big.html'), qr/200 OK/, 'head request'); # once proxy_read_timeout expires, nginx will call # ngx_http_finalize_upstream_request() with u->pipe->downstream_error set -# and rc = NGX_HTTP_GATEWAY_BAD_GATEWAY; after revision ad3f342f14ba046c this -# will result in ngx_http_finalize_request(NGX_HTTP_GATEWAY_BAD_GATEWAY), +# and rc = NGX_HTTP_BAD_GATEWAY; after revision ad3f342f14ba046c this +# will result in ngx_http_finalize_request(NGX_HTTP_BAD_GATEWAY), # leading to an attempt to return additional error response and # the "header already sent" alert; fixed in 93abb5a855d6 diff --git a/tests/nginx-tests/nginx-tests/proxy_cache_min_free.t b/tests/nginx-tests/nginx-tests/proxy_cache_min_free.t new file mode 100644 index 0000000000..6daeebf2d6 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/proxy_cache_min_free.t @@ -0,0 +1,76 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http proxy cache, min_free parameter. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy cache/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + proxy_cache_path %%TESTDIR%%/cache levels=1:2 min_free=4k + keys_zone=NAME:1m; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + proxy_pass http://127.0.0.1:8081; + + proxy_cache NAME; + + proxy_cache_valid any 1m; + + add_header X-Cache-Status $upstream_cache_status; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location / { } + } +} + +EOF + +$t->write_file('t.html', 'SEE-THIS'); +$t->run()->plan(2); + +############################################################################### + +like(http_get('/t.html'), qr/SEE-THIS/, 'proxy request'); + +$t->write_file('t.html', 'NOOP'); +like(http_get('/t.html'), qr/SEE-THIS/, 'proxy request cached'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_cache_revalidate.t b/tests/nginx-tests/nginx-tests/proxy_cache_revalidate.t index 3a6565b88a..ff828a26ee 100644 --- a/tests/nginx-tests/nginx-tests/proxy_cache_revalidate.t +++ b/tests/nginx-tests/nginx-tests/proxy_cache_revalidate.t @@ -14,7 +14,7 @@ use Test::More; BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; -use Test::Nginx qw/ :DEFAULT :gzip /; +use Test::Nginx; ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_cache_use_stale.t b/tests/nginx-tests/nginx-tests/proxy_cache_use_stale.t index fc85669f92..812018b26a 100644 --- a/tests/nginx-tests/nginx-tests/proxy_cache_use_stale.t +++ b/tests/nginx-tests/nginx-tests/proxy_cache_use_stale.t @@ -148,10 +148,9 @@ $t->write_file('t7.html', 'SEE-THIS' x 1024); $t->write_file('t9.html', 'SEE-THIS' x 1024); $t->write_file('ssi.html', 'xxx xxx'); $t->write_file('escape.html', 'SEE-THIS'); -$t->write_file('escape html', 'SEE-THIS'); $t->write_file('regexp.html', 'SEE-THIS'); -$t->run()->plan(35); +$t->run()->plan(34); ############################################################################### @@ -171,15 +170,19 @@ get('/t7.html', 'max-age=1, stale-while-revalidate=10'); http_get('/ssi.html'); get('/updating/t.html', 'max-age=1'); get('/updating/t2.html', 'max-age=1, stale-while-revalidate=2'); +get('/updating/tt.html', 'max-age=1, stale-if-error=5'); get('/t8.html', 'stale-while-revalidate=10'); get('/escape.htm%6C', 'max-age=1, stale-while-revalidate=10'); -get('/escape html', 'max-age=1, stale-while-revalidate=10'); get('/regexp.html', 'max-age=1, stale-while-revalidate=10'); sleep 2; -like(http_get('/t.html?e=1'), qr/STALE/, 's-i-e - stale'); -like(http_get('/tt.html?e=1'), qr/STALE/, 's-i-e - stale 2'); +# stale 5xx response is ignored since 1.19.3, +# "proxy_cache_use_stale updating;" allows to get it still + +like(http_get('/t.html?e=1'), qr/ 500 /, 's-i-e - stale 5xx ignore'); +like(http_get('/tt.html?e=1'), qr/ 500 /, 's-i-e - stale 5xx ignore 2'); +like(http_get('/updating/tt.html'), qr/STALE/, 's-i-e - stale 5xx updating'); like(http_get('/t.html'), qr/REVALIDATED/, 's-i-e - revalidated'); like(http_get('/t2.html?e=1'), qr/STALE/, 's-w-r - revalidate error'); @@ -190,21 +193,15 @@ like(get('/t4.html', 'max-age=1, stale-while-revalidate=2'), qr/STALE/, 's-w-r - unconditional revalidate'); like(http_get('/t4.html'), qr/HIT/, 's-w-r - unconditional revalidated'); -like(http_get('/t5.html?e=1'), qr/STALE/, +like(http_get('/t5.html?e=1'), qr/ 500 /, 's-w-r - foreground revalidate error'); like(http_get('/t5.html'), qr/REVALIDATED/, 's-w-r - foreground revalidated'); # proxy_pass to regular expression with named and positional captures like(http_get('/regexp.html'), qr/STALE/, 's-w-r - regexp background update'); - -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.15.8'); - like(http_get('/regexp.html'), qr/HIT/, 's-w-r - regexp revalidated'); -} - # UPDATING while s-w-r $t->write_file('t6.html', 'SEE-THAT'); @@ -239,12 +236,12 @@ like(http_get('/updating/t2.html'), qr/STALE/, # before 1.13.1, if stale response was not sent in one pass, its remaining # part was blocked and not sent until background update has been finished -$t->write_file('t7.html', 'SEE-THAT' x 1024); +$t->write_file('t7.html', 'SEE-THAT' x 256); my $r = read_all(get('/t7.html?lim=1', 'max-age=1', start => 1)); like($r, qr/STALE.*^(SEE-THIS){1024}$/ms, 's-w-r - stale response not blocked'); -$t->write_file('t9.html', 'SEE-THAT' x 1024); +$t->write_file('t9.html', 'SEE-THAT' x 256); $t->write_file('ssi.html', 'xxx xxx'); $r = read_all(http_get('/ssi.html', start => 1)); @@ -264,15 +261,11 @@ like(http_get('/t2.html?if=1'), qr/HIT/, 'background update in if - updated'); # ticket #1430, uri escaping in cloned subrequests $t->write_file('escape.html', 'SEE-THAT'); -$t->write_file('escape html', 'SEE-THAT'); get('/escape.htm%6C', 'max-age=1'); -get('/escape html', 'max-age=1'); like(http_get('/escape.htm%6C'), qr/HIT/, 'escaped after escaped'); like(http_get('/escape.html'), qr/MISS/, 'unescaped after escaped'); -like(http_get('/escape html'), qr/HIT/, 'space after escaped space'); -like(http_get('/escape%20html'), qr/HIT/, 'escaped space after escaped space'); ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_cache_vary.t b/tests/nginx-tests/nginx-tests/proxy_cache_vary.t index a087a40612..88807a6cbb 100644 --- a/tests/nginx-tests/nginx-tests/proxy_cache_vary.t +++ b/tests/nginx-tests/nginx-tests/proxy_cache_vary.t @@ -22,7 +22,7 @@ select STDERR; $| = 1; select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/http proxy cache gzip rewrite/) - ->plan(42)->write_file_expand('nginx.conf', <<'EOF'); + ->plan(52)->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -92,6 +92,18 @@ http { gzip off; add_header Vary ",, Accept-encoding , ,"; } + + location /multi { + gzip off; + add_header Vary Accept-Encoding; + add_header Vary Foo; + } + + location /cold { + expires max; + add_header Vary $arg_vary; + add_header Xtra $arg_xtra; + } } } @@ -100,6 +112,8 @@ EOF $t->write_file('index.html', 'SEE-THIS'); $t->write_file('asterisk', 'SEE-THIS'); $t->write_file('complex', 'SEE-THIS'); +$t->write_file('multi', 'SEE-THIS'); +$t->write_file('cold', 'SEE-THIS'); $t->run(); @@ -248,6 +262,45 @@ like(get('/', 'bar,foo'), qr/HIT/ms, 'normalize order'); } +# Multiple Vary headers (ticket #1423). + +like(get('/multi', 'foo'), qr/MISS/ms, 'multi first'); +like(get('/multi', 'foo'), qr/HIT/ms, 'multi second'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like(get('/multi', 'bar'), qr/MISS/ms, 'multi other'); + +} + +# keep c->body_start when Vary changes (ticket #2029) + +# before 1.19.3, this prevented updating c->body_start of a main key +# triggering "cache file .. has too long header" critical errors + +get1('/cold?vary=z', 'z:1'); +like(get1('/cold?vary=x,y', 'x:1'), qr/MISS/, 'change first'); +like(get1('/cold?vary=x,y', 'x:1'), qr/HIT/, 'change first cached'); + +like(get1('/cold?vary=x,y&xtra=1', 'x:2'), qr/MISS/, 'change second'); +like(get1('/cold?vary=x,y&xtra=1', 'x:2'), qr/HIT/, 'change second cached'); + +$t->stop(); +$t->run(); + +# reset c->body_start when loading a secondary key variant + +# before 1.19.3, it was loaded using a variant stored with a main key +# triggering "cache file .. has too long header" critical errors + +like(get1('/cold?vary=x,y', 'x:1'), qr/HIT/, 'cold first'); +like(get1('/cold?vary=x,y&xtra=1', 'x:2'), qr/HIT/, 'cold second'); + +$t->stop(); + +like(`grep -F '[crit]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no crit'); + ############################################################################### sub get { @@ -261,4 +314,15 @@ Accept-Encoding: $extra EOF } +sub get1 { + my ($url, $extra) = @_; + return http(<new()->has(qw/http proxy/)->plan(1); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + proxy_buffer_size 128; + proxy_buffers 4 128; + + location / { + proxy_pass http://127.0.0.1:8081; + proxy_read_timeout 1s; + } + } +} + +EOF + +$t->run_daemon(\&http_chunked_daemon); +$t->run()->waitforsocket('127.0.0.1:' . port(8081)); + +############################################################################### + +like(http_get('/'), qr/200 OK(?!.*zzz)/s, 'chunked with extra data'); + +############################################################################### + +sub http_chunked_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . port(8081), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + while (<$client>) { + last if (/^\x0d?\x0a?$/); + } + + # return a large response start to allocate + # multiple buffers; stop at the buffer end + + print $client "" + . "HTTP/1.1 200 OK" . CRLF + . "Connection: close" . CRLF + . "Transfer-Encoding: chunked" . CRLF . CRLF + . "80" . CRLF . ("x" x 126) . CRLF . CRLF + . "80" . CRLF . ("x" x 126) . CRLF . CRLF + . "80" . CRLF . ("x" x 126) . CRLF . CRLF + . "80" . CRLF . ("x" x 126) . CRLF . CRLF + . "20" . CRLF . ("x" x 30) . CRLF . CRLF; + + select(undef, undef, undef, 0.3); + + # fill three full buffers here, so they are + # processed in order, regardless of the + # p->upstream_done flag set + + print $client "" + . "75" . CRLF . ("y" x 115) . CRLF . CRLF + . "0" . CRLF . CRLF + . "75" . CRLF . ("z" x 115) . CRLF . CRLF + . "0" . CRLF . CRLF + . "75" . CRLF . ("z" x 115) . CRLF . CRLF + . "0" . CRLF . CRLF; + + close $client; + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_cookie.t b/tests/nginx-tests/nginx-tests/proxy_cookie.t index 7e541b1b21..d98952cab4 100644 --- a/tests/nginx-tests/nginx-tests/proxy_cookie.t +++ b/tests/nginx-tests/nginx-tests/proxy_cookie.t @@ -51,6 +51,13 @@ http { proxy_cookie_path /$server_name/ /new/$server_name/; proxy_cookie_path ~^/regex/(.+)$ /$1; proxy_cookie_path ~*^/caseless/(.+)$ /$1; + + location /off/ { + proxy_pass http://127.0.0.1:8081; + + proxy_cookie_domain off; + proxy_cookie_path off; + } } } @@ -73,7 +80,7 @@ http { EOF -$t->run()->plan(8); +$t->run()->plan(9); ############################################################################### @@ -100,6 +107,9 @@ is(http_get_set_cookie('/?path=/CASEless/test.html'), is(http_get_set_cookie('/?domain=www.example.org&path=/path/test.html'), 'v=path=domain=; Domain=example.com; Path=/new/test.html', 'domain and path rewrite'); +is(http_get_set_cookie('/off/?domain=www.example.org&path=/path/test.html'), + 'v=path=domain=; Domain=www.example.org; Path=/path/test.html', + 'domain and path rewrite off'); ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_cookie_flags.t b/tests/nginx-tests/nginx-tests/proxy_cookie_flags.t new file mode 100644 index 0000000000..1bf18c9e3b --- /dev/null +++ b/tests/nginx-tests/nginx-tests/proxy_cookie_flags.t @@ -0,0 +1,143 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for the proxy_cookie_flags directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + proxy_pass http://127.0.0.1:8081; + + proxy_cookie_flags a secure httponly samesite=none; + proxy_cookie_flags b secure httponly samesite=lax; + proxy_cookie_flags c secure httponly samesite=strict; + proxy_cookie_flags d nosecure nohttponly nosamesite; + + proxy_cookie_flags $arg_complex secure; + proxy_cookie_flags ~BAR httponly; + + location /off/ { + proxy_pass http://127.0.0.1:8081; + proxy_cookie_flags off; + } + } + + location /var/ { + proxy_pass http://127.0.0.1:8081; + proxy_cookie_flags $arg_v $arg_f1 $arg_f2 $arg_f3; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location / { + set $c "$arg_v$arg_complex=path=domain=; Domain=example.org$arg_f"; + add_header Set-Cookie $c; + return 200 OK; + } + } +} + +EOF + +$t->run()->plan(14); + +############################################################################### + +is(http_get_set_cookie('/?v=a'), + 'a=path=domain=; Domain=example.org; Secure; HttpOnly; SameSite=None', + 'flags set all'); +is(http_get_set_cookie('/?v=b'), + 'b=path=domain=; Domain=example.org; Secure; HttpOnly; SameSite=Lax', + 'flags set lax'); +is(http_get_set_cookie('/?v=c'), + 'c=path=domain=; Domain=example.org; Secure; HttpOnly; SameSite=Strict', + 'flags set strict'); + +# edit already set flags + +is(http_get_set_cookie('/?v=a&f=;Secure;HttpOnly;SameSite=Lax'), + 'a=path=domain=; Domain=example.org; Secure; HttpOnly; SameSite=None', + 'flags reset all'); +is(http_get_set_cookie('/?v=b&f=;Secure;HttpOnly;SameSite=None'), + 'b=path=domain=; Domain=example.org; Secure; HttpOnly; SameSite=Lax', + 'flags reset lax'); +is(http_get_set_cookie('/?v=c&f=;Secure;HttpOnly;SameSite=None'), + 'c=path=domain=; Domain=example.org; Secure; HttpOnly; SameSite=Strict', + 'flags reset strict'); + +is(http_get_set_cookie('/?v=d&f=;secure;httponly;samesite=lax'), + 'd=path=domain=; Domain=example.org', + 'flags remove'); + +is(http_get_set_cookie('/?v=nx&f=;samesite=none'), + 'nx=path=domain=; Domain=example.org;samesite=none', 'flags no match'); + +is(http_get_set_cookie('/?complex=v'), + 'v=path=domain=; Domain=example.org; Secure', 'flags variable'); +is(http_get_set_cookie('/?v=foobarbaz'), + 'foobarbaz=path=domain=; Domain=example.org; HttpOnly', 'flags regex'); + +is(http_get_set_cookie('/off/?v=a'), 'a=path=domain=; Domain=example.org', + 'flags off'); + +# variables in flags + +is(http_get_set_cookie('/var/?v=v&f1=secure&f2=httponly&f3=samesite=none'), + 'v=path=domain=; Domain=example.org; Secure; HttpOnly; SameSite=None', + 'flags set'); +is(http_get_set_cookie('/var/?v=v&f=;Secure;HttpOnly;SameSite=Lax' . + '&f1=secure&f2=httponly&f3=samesite=none'), + 'v=path=domain=; Domain=example.org; Secure; HttpOnly; SameSite=None', + 'flags reset'); +is(http_get_set_cookie('/var/?v=v&f=;secure;httponly;samesite=lax' . + '&f1=nosecure&f2=nohttponly&f3=nosamesite'), + 'v=path=domain=; Domain=example.org', + 'flags remove'); + +############################################################################### + +sub http_get_set_cookie { + my ($uri) = @_; + http_get($uri) =~ /^Set-Cookie:\s(.+?)\x0d?$/mi; + return $1; +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_duplicate_headers.t b/tests/nginx-tests/nginx-tests/proxy_duplicate_headers.t new file mode 100644 index 0000000000..b4d7e56893 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/proxy_duplicate_headers.t @@ -0,0 +1,175 @@ +#!/usr/bin/perl + +# (C) Maxim Dounin + +# Test for http backend returning response with invalid and duplicate +# headers. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(8); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + proxy_pass http://127.0.0.1:8081; + proxy_read_timeout 1s; + } + } +} + +EOF + +$t->run_daemon(\&http_daemon); +$t->run()->waitforsocket('127.0.0.1:' . port(8081)); + +############################################################################### + +like(http_get('/'), qr/200 OK/, 'normal'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like(http_get('/invalid-length'), qr/502 Bad/, 'invalid length'); +like(http_get('/duplicate-length'), qr/502 Bad/, 'duplicate length'); +like(http_get('/unknown-transfer-encoding'), qr/502 Bad/, + 'unknown transfer encoding'); +like(http_get('/duplicate-transfer-encoding'), qr/502 Bad/, + 'duplicate transfer encoding'); +like(http_get('/length-and-transfer-encoding'), qr/502 Bad/, + 'length and transfer encoding'); +like(http_get('/transfer-encoding-and-length'), qr/502 Bad/, + 'transfer encoding and length'); + +like(http_get('/duplicate-expires'), qr/Expires: foo(?!.*bar)/s, + 'duplicate expires ignored'); + +} + +############################################################################### + +sub http_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . port(8081), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + my $headers = ''; + my $uri = ''; + + while (<$client>) { + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; + + if ($uri eq '/') { + + print $client + 'HTTP/1.1 200 OK' . CRLF . + 'Connection: close' . CRLF . + 'Content-Length: 0' . CRLF . CRLF; + + } elsif ($uri eq '/invalid-length') { + + print $client + 'HTTP/1.1 200 OK' . CRLF . + 'Connection: close' . CRLF . + 'Content-Length: foo' . CRLF . CRLF; + + } elsif ($uri eq '/duplicate-length') { + + print $client + 'HTTP/1.1 200 OK' . CRLF . + 'Connection: close' . CRLF . + 'Content-Length: 0' . CRLF . + 'Content-Length: 0' . CRLF . CRLF; + + } elsif ($uri eq '/unknown-transfer-encoding') { + + print $client + 'HTTP/1.1 200 OK' . CRLF . + 'Connection: close' . CRLF . + 'Transfer-Encoding: foo' . CRLF . CRLF; + + } elsif ($uri eq '/duplicate-transfer-encoding') { + + print $client + 'HTTP/1.1 200 OK' . CRLF . + 'Connection: close' . CRLF . + 'Transfer-Encoding: chunked' . CRLF . + 'Transfer-Encoding: chunked' . CRLF . CRLF . + '0' . CRLF . CRLF; + + } elsif ($uri eq '/length-and-transfer-encoding') { + + print $client + 'HTTP/1.1 200 OK' . CRLF . + 'Connection: close' . CRLF . + 'Content-Length: 0' . CRLF . + 'Transfer-Encoding: chunked' . CRLF . CRLF . + '0' . CRLF . CRLF; + + } elsif ($uri eq '/transfer-encoding-and-length') { + + print $client + 'HTTP/1.1 200 OK' . CRLF . + 'Connection: close' . CRLF . + 'Transfer-Encoding: chunked' . CRLF . + 'Content-Length: 0' . CRLF . CRLF . + '0' . CRLF . CRLF; + + } elsif ($uri eq '/duplicate-expires') { + + print $client + 'HTTP/1.1 200 OK' . CRLF . + 'Connection: close' . CRLF . + 'Expires: foo' . CRLF . + 'Expires: bar' . CRLF . CRLF; + + } + + close $client; + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_extra_data.t b/tests/nginx-tests/nginx-tests/proxy_extra_data.t new file mode 100644 index 0000000000..4345f1335b --- /dev/null +++ b/tests/nginx-tests/nginx-tests/proxy_extra_data.t @@ -0,0 +1,204 @@ +#!/usr/bin/perl + +# (C) Maxim Dounin +# (C) Nginx, Inc. + +# Tests for http backend with extra data. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new() + ->has(qw/http proxy cache rewrite addition/)->plan(22) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + proxy_cache_path cache keys_zone=one:1m; + proxy_cache_key $request_uri; + proxy_cache_valid any 1m; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + proxy_pass http://127.0.0.1:8081; + add_after_body /after; + } + + location /unbuf/ { + proxy_pass http://127.0.0.1:8081; + proxy_buffering off; + add_after_body /after; + } + + location /head/ { + proxy_pass http://127.0.0.1:8081; + proxy_cache one; + add_after_body /after; + } + + location /after { + return 200 ":after\n"; + } + } +} + +EOF + +$t->run_daemon(\&http_daemon); +$t->run()->waitforsocket('127.0.0.1:' . port(8081)); + +############################################################################### + +like(http_get('/'), qr/SEE-THIS(?!-BUT-NOT-THIS)/, 'response with extra data'); +like(http_get('/short'), qr/SEE-THIS(?!.*:after)/s, 'too short response'); +like(http_get('/empty'), qr/200 OK(?!.*:after)/s, 'empty too short response'); + +like(http_head('/'), qr/200 OK(?!.*SEE-THIS)/s, 'no data in HEAD'); +like(http_head('/short'), qr/200 OK(?!.*SEE-THIS)/s, 'too short to HEAD'); +like(http_head('/empty'), qr/200 OK/, 'empty response to HEAD'); + +# unbuffered responses + +like(http_get('/unbuf/'), qr/SEE-THIS(?!-BUT-NOT-THIS)/, + 'unbuffered with extra data'); +like(http_get('/unbuf/short'), qr/SEE-THIS(?!.*:after)/s, + 'unbuffered too short response'); +like(http_get('/unbuf/empty'), qr/200 OK(?!.*:after)/s, + 'unbuffered empty too short response'); + +like(http_head('/unbuf/'), qr/200 OK(?!.*SEE-THIS)/s, + 'unbuffered no data in HEAD'); +like(http_head('/unbuf/short'), qr/200 OK(?!.*SEE-THIS)/s, + 'unbuffered too short response to HEAD'); +like(http_head('/unbuf/empty'), qr/200 OK/, + 'unbuffered empty response to HEAD'); + +# caching of responsses to HEAD requests + +like(http_head('/head/empty'), qr/200 OK(?!.*SEE-THIS)/s, 'head no body'); +like(http_head('/head/matching'), qr/200 OK(?!.*SEE-THIS)/s, 'head matching'); +like(http_head('/head/extra'), qr/200 OK(?!.*SEE-THIS)/s, 'head extra'); +like(http_head('/head/short'), qr/200 OK(?!.*SEE-THIS)/s, 'head too short'); + +like(http_get('/head/empty'), qr/SEE-THIS/, 'head no body cached'); +like(http_get('/head/matching'), qr/SEE-THIS/, 'head matching cached'); +like(http_get('/head/extra'), qr/SEE-THIS(?!-BUT-NOT-THIS)/s, + 'head extra cached'); +like(http_get('/head/short'), qr/SEE-THIS(?!.*:after)/s, + 'head too short cached'); + +# "zero size buf" alerts (ticket #2117) + +like(http_get('/zero'), qr/200 OK(?!.*NOT-THIS)/s, 'zero size'); +like(http_get('/unbuf/zero'), qr/200 OK(?!.*NOT-THIS)/s, + 'unbuffered zero size'); + +############################################################################### + +sub http_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => '127.0.0.1:' . port(8081), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + my ($uri, $head); + + while (my $c = $server->accept()) { + $c->autoflush(1); + + my $headers = ''; + my $uri = ''; + + while (<$c>) { + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; + $uri =~ s!^/unbuf!!; + + $head = ($headers =~ /^HEAD/); + + if ($uri eq '/') { + $c->print("HTTP/1.1 200 OK\n"); + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 8\n\n"); + $c->print("SEE-THIS-BUT-NOT-THIS\n"); + + } elsif ($uri eq '/zero') { + $c->print("HTTP/1.1 200 OK\n"); + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 0\n\n"); + $c->print("NOT-THIS\n"); + + } elsif ($uri eq '/short') { + $c->print("HTTP/1.1 200 OK\n"); + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 100\n\n"); + $c->print("SEE-THIS-TOO-SHORT-RESPONSE\n"); + + } elsif ($uri eq '/empty') { + $c->print("HTTP/1.1 200 OK\n"); + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 100\n\n"); + + } elsif ($uri eq '/head/empty') { + $c->print("HTTP/1.1 200 OK\n"); + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 8\n\n"); + $c->print("SEE-THIS") unless $head; + + } elsif ($uri eq '/head/matching') { + $c->print("HTTP/1.1 200 OK\n"); + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 8\n\n"); + $c->print("SEE-THIS"); + + } elsif ($uri eq '/head/extra') { + $c->print("HTTP/1.1 200 OK\n"); + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 8\n\n"); + $c->print("SEE-THIS-BUT-NOT-THIS\n"); + + } elsif ($uri eq '/head/short') { + $c->print("HTTP/1.1 200 OK\n"); + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 100\n\n"); + $c->print("SEE-THIS\n"); + } + + close $c; + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_force_ranges.t b/tests/nginx-tests/nginx-tests/proxy_force_ranges.t index 178fce0278..3453f65582 100644 --- a/tests/nginx-tests/nginx-tests/proxy_force_ranges.t +++ b/tests/nginx-tests/nginx-tests/proxy_force_ranges.t @@ -13,6 +13,8 @@ use strict; use Test::More; +use Socket qw/ $CRLF /; + BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; @@ -23,7 +25,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http proxy cache/)->plan(6) +my $t = Test::Nginx->new()->has(qw/http proxy cache/)->plan(7) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -50,6 +52,7 @@ http { location /proxy/ { proxy_pass http://127.0.0.1:8081/; proxy_force_ranges on; + add_trailer X-Trailer ""; } location /cache/ { @@ -104,6 +107,11 @@ like(http_get_range('/proxy/t.html', "Range: bytes=4-\nIf-Range: \"59a5401c-8\""), qr/^THIS/m, 'if-range etag proxy'); +# range sent using chunked transfer encoding + +like(http_get_range('/proxy/t.html', 'Range: bytes=-2'), + qr/2${CRLF}IS${CRLF}0$CRLF$CRLF$/, 'no dublicate final chunk'); + ############################################################################### sub http_get_range { diff --git a/tests/nginx-tests/nginx-tests/proxy_implicit.t b/tests/nginx-tests/nginx-tests/proxy_implicit.t new file mode 100644 index 0000000000..e5b9cb4730 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/proxy_implicit.t @@ -0,0 +1,105 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for an upstream implicitly defined by proxy_pass. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use Socket qw/ SOCK_STREAM IPPROTO_TCP AF_INET6 /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { die if $Socket::VERSION < 1.96; }; +plan(skip_all => 'Socket too old for getaddrinfo') if $@; + +my $t = Test::Nginx->new()->has(qw/http proxy/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + listen [::1]:%%PORT_8080%%; + server_name localhost; + + location / { + proxy_pass http://localhost:%%PORT_8080%%/stub; + proxy_next_upstream http_404; + add_header X-Addr $upstream_addr always; + } + + location /var { + proxy_pass http://$arg_b/stub; + proxy_next_upstream http_404; + add_header X-Addr $upstream_addr always; + } + + location /stub { } + } +} + +EOF + +$t->try_run('no inet6 support')->plan(3); + +############################################################################### + +my $p = port(8080); +my @addrs = resolve('localhost'); +my $exp = qr/$addrs[0]:$p/ if @addrs == 1; +my $v1 = "$addrs[0]:$p", my $v2 = "$addrs[1]:$p" if @addrs == 2; +$exp = qr/\Q$v1, $v2\E|\Q$v2, $v1\E/ if @addrs == 2; +die "too many addresses in localhost" if @addrs > 2; + +like(http_get('/'), qr/Not Found/, 'implicit upstream'); +like(http_get('/'), $exp, 'implicit upstream all tried'); +like(http_get("/var?b=localhost:$p"), qr/Not Found/, + 'implicit upstream by variable'); + +############################################################################### + +sub resolve { + my ($name) = @_; + + my $ai_addrconfig = eval { Socket::AI_ADDRCONFIG() }; + my ($err, @res) = Socket::getaddrinfo($name, "", + { socktype => SOCK_STREAM, protocol => IPPROTO_TCP, + flags => $ai_addrconfig }); + die "Cannot getaddrinfo - $err" if $err; + + my @addrs; + foreach my $ai (@res) { + my ($err, $addr) = Socket::getnameinfo($ai->{addr}, + Socket::NI_NUMERICHOST(), Socket::NIx_NOSERV()); + die "Cannot getnameinfo - $err" if $err; + $addr = '[' . $addr . ']' if $ai->{family} == AF_INET6; + push @addrs, $addr; + } + return @addrs; +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_intercept_errors.t b/tests/nginx-tests/nginx-tests/proxy_intercept_errors.t new file mode 100644 index 0000000000..a0579f8deb --- /dev/null +++ b/tests/nginx-tests/nginx-tests/proxy_intercept_errors.t @@ -0,0 +1,105 @@ +#!/usr/bin/perl + +# (C) Maxim Dounin + +# Tests for http proxy module, proxy_intercept_errors directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/)->plan(4); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + proxy_pass http://127.0.0.1:8081; + proxy_intercept_errors on; + error_page 401 500 /intercepted; + } + + location = /intercepted { + return 200 "intercepted\n"; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location / { + return 404 "SEE-THIS"; + } + + location /500 { + return 500; + } + + location /auth { + add_header WWW-Authenticate foo always; + return 401; + } + + location /auth-multi { + add_header WWW-Authenticate foo always; + add_header WWW-Authenticate bar always; + return 401; + } + } +} + +EOF + +$t->run(); + +############################################################################### + +# make sure errors without error_page set are not intercepted + +like(http_get('/'), qr/SEE-THIS/, 'not intercepted'); + +# make sure errors with error_page are intercepted + +like(http_get('/500'), qr/500.*intercepted/s, 'intercepted 500'); +like(http_get('/auth'), qr/401.*WWW-Authenticate.*intercepted/s, + 'intercepted 401'); + +# make sure multiple WWW-Authenticate headers are returned +# along with intercepted response (ticket #485) + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +like(http_get('/auth-multi'), qr/401.*WWW-Authenticate: foo.*bar.*intercept/s, + 'intercepted 401 multi'); + +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_limit_rate.t b/tests/nginx-tests/nginx-tests/proxy_limit_rate.t index 060ad1bb5a..5fde7151e7 100644 --- a/tests/nginx-tests/nginx-tests/proxy_limit_rate.t +++ b/tests/nginx-tests/nginx-tests/proxy_limit_rate.t @@ -48,7 +48,7 @@ http { location / { proxy_pass http://127.0.0.1:8080/data; proxy_buffer_size 4k; - proxy_limit_rate 12000; + proxy_limit_rate 20000; add_header X-Msec $msec; } @@ -57,7 +57,7 @@ http { proxy_set_header Connection ""; proxy_pass http://u/data; proxy_buffer_size 4k; - proxy_limit_rate 12000; + proxy_limit_rate 20000; add_header X-Msec $msec; } @@ -80,7 +80,7 @@ my $diff = time() - $t1; # four chunks are split with three 1s delays -cmp_ok($diff, '>=', 3, 'proxy_limit_rate'); +cmp_ok($diff, '>=', 1, 'proxy_limit_rate'); like($r, qr/^(XXXXXXXXXX){4000}\x0d?\x0a?$/m, 'response body'); # in case keepalive connection was saved with the delayed flag, diff --git a/tests/nginx-tests/nginx-tests/proxy_max_temp_file_size.t b/tests/nginx-tests/nginx-tests/proxy_max_temp_file_size.t new file mode 100644 index 0000000000..020c0f3b7b --- /dev/null +++ b/tests/nginx-tests/nginx-tests/proxy_max_temp_file_size.t @@ -0,0 +1,84 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http proxy module, proxy_max_temp_file_size directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx qw/ :DEFAULT http_content /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(4); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080 sndbuf=32k; + server_name localhost; + + proxy_buffer_size 4k; + proxy_buffers 8 4k; + + location / { + proxy_max_temp_file_size 4k; + proxy_pass http://127.0.0.1:8081/; + } + + location /off/ { + proxy_max_temp_file_size 0; + proxy_pass http://127.0.0.1:8081/; + } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location / { } + } +} + +EOF + +$t->write_file('1', 'X' x (1024 * 1024)); +$t->run(); + +############################################################################### + +# test that the response is wholly proxied when all event pipe buffers are full + +my $body = http_content(http_get('/1', sleep => 0.4)); +like($body, qr/^X+$/m, 'no pipe bufs - body'); +is(length($body), 1024 * 1024, 'no pipe bufs - body length'); + +# also with disabled proxy temp file + +$body = http_content(http_get('/off/1', sleep => 0.4)); +like($body, qr/^X+$/m, 'no temp file - body'); +is(length($body), 1024 * 1024, 'no temp file - body length'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_merge_headers.t b/tests/nginx-tests/nginx-tests/proxy_merge_headers.t index 434e9d1cab..7141667a2f 100644 --- a/tests/nginx-tests/nginx-tests/proxy_merge_headers.t +++ b/tests/nginx-tests/nginx-tests/proxy_merge_headers.t @@ -10,6 +10,7 @@ use warnings; use strict; use Test::More; +use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -21,7 +22,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http proxy cache rewrite/)->plan(7) +my $t = Test::Nginx->new()->has(qw/http proxy cache rewrite/)->plan(11) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -64,6 +65,16 @@ http { proxy_pass http://127.0.0.1:8081; proxy_set_body "body"; } + + location /passdate/ { + proxy_pass http://127.0.0.1:8082; + proxy_pass_header Date; + proxy_pass_header Server; + + location /passdate/no/ { + proxy_pass http://127.0.0.1:8082; + } + } } server { @@ -80,8 +91,11 @@ http { EOF +$t->run_daemon(\&http_daemon); $t->run(); +$t->waitforsocket('127.0.0.1:' . port(8082)); + ############################################################################### like(http_get_ims('/'), qr/ims=;blah=blah;/, @@ -98,6 +112,11 @@ like(http_get('/nested/'), qr/X-Pad/, 'proxy_pass_header nested'); unlike(http_get('/'), qr/X-Hidden/, 'proxy_hide_header inherited'); unlike(http_get('/nested/'), qr/X-Hidden/, 'proxy_hide_header nested'); +like(http_get('/passdate/'), qr/Date: passed/, 'proxy_pass_header date'); +like(http_get('/passdate/'), qr/Server: passed/, 'proxy_pass_header server'); +unlike(http_get('/passdate/no/'), qr/Date/, 'proxy_pass_header no date'); +unlike(http_get('/passdate/no/'), qr/Server/, 'proxy_pass_header no server'); + ############################################################################### sub http_get_ims { @@ -112,3 +131,45 @@ EOF } ############################################################################### + +sub http_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => '127.0.0.1', + LocalPort => port(8082), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + my $headers = ''; + my $uri = ''; + + while (<$client>) { + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; + + if ($uri =~ 'no') { + print $client + 'HTTP/1.0 200 OK' . CRLF . CRLF; + + } else { + print $client + 'HTTP/1.0 200 OK' . CRLF . + 'Date: passed' . CRLF . + 'Server: passed' . CRLF . CRLF; + } + + close $client; + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_next_upstream.t b/tests/nginx-tests/nginx-tests/proxy_next_upstream.t index 6315b77c0e..a457627678 100644 --- a/tests/nginx-tests/nginx-tests/proxy_next_upstream.t +++ b/tests/nginx-tests/nginx-tests/proxy_next_upstream.t @@ -21,7 +21,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http proxy rewrite/)->plan(7); +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/)->plan(8); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -46,6 +46,11 @@ http { server 127.0.0.1:8082; } + upstream u3 { + server 127.0.0.1:8081; + server 127.0.0.1:8082 down; + } + server { listen 127.0.0.1:8080; server_name localhost; @@ -65,6 +70,11 @@ http { location /all/404 { return 200 "$upstream_addr\n"; } + + location /down { + proxy_pass http://u3; + proxy_next_upstream http_404; + } } server { @@ -134,4 +144,9 @@ like(http_get('/all/rr'), qr/^127.0.0.1:($p1, 127.0.0.1:$p2|$p2, 127.0.0.1:$p1)$/mi, 'all tried once'); +# make sure backend marked as down doesn't count towards "no live upstreams" +# after all backends are tried with http_404 + +like(http_get('/down/'), qr/Not Found/, 'all tried with down'); + ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_protocol.t b/tests/nginx-tests/nginx-tests/proxy_protocol.t index 75a87c24cb..0f73814a07 100644 --- a/tests/nginx-tests/nginx-tests/proxy_protocol.t +++ b/tests/nginx-tests/nginx-tests/proxy_protocol.t @@ -26,7 +26,7 @@ select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/http access realip/); -$t->write_file_expand('nginx.conf', <<'EOF')->plan(18); +$t->write_file_expand('nginx.conf', <<'EOF')->plan(20); %%TEST_GLOBALS%% @@ -38,28 +38,32 @@ events { http { %%TEST_GLOBALS_HTTP%% - log_format pp '$remote_addr $request'; + log_format pp $remote_addr:$remote_port; server { listen 127.0.0.1:8080 proxy_protocol; server_name localhost; set_real_ip_from 127.0.0.1/32; - add_header X-IP $remote_addr; - add_header X-PP $proxy_protocol_addr; + add_header X-IP $remote_addr!$remote_port; + add_header X-PP $proxy_protocol_addr!$proxy_protocol_port; location /pp { real_ip_header proxy_protocol; error_page 404 =200 /t1; - access_log %%TESTDIR%%/pp.log pp; location /pp_4 { deny 192.0.2.1/32; + access_log %%TESTDIR%%/pp4.log pp; } + location /pp_6 { deny 2001:DB8::1/128; + access_log %%TESTDIR%%/pp6.log pp; } } + + location / { } } } @@ -70,8 +74,8 @@ $t->run(); ############################################################################### -my $tcp4 = 'PROXY TCP4 192.0.2.1 192.0.2.2 1234 5678' . CRLF; -my $tcp6 = 'PROXY TCP6 2001:Db8::1 2001:Db8::2 1234 5678' . CRLF; +my $tcp4 = 'PROXY TCP4 192.0.2.1 192.0.2.2 123 5678' . CRLF; +my $tcp6 = 'PROXY TCP6 2001:Db8::1 2001:Db8::2 123 5678' . CRLF; my $unk1 = 'PROXY UNKNOWN' . CRLF; my $unk2 = 'PROXY UNKNOWN 1 2 3 4 5 6' . CRLF; my $r; @@ -80,28 +84,33 @@ my $r; $r = pp_get('/t1', $tcp4); like($r, qr/SEE-THIS/, 'tcp4 request'); -like($r, qr/X-PP: 192.0.2.1/, 'tcp4 proxy'); -unlike($r, qr/X-IP: 192.0.2.1/, 'tcp4 client'); +like($r, qr/X-PP: 192.0.2.1!123\x0d/, 'tcp4 proxy'); +unlike($r, qr/X-IP: (192.0.2.1|[^!]+!123\x0d)/, 'tcp4 client'); $r = pp_get('/t1', $tcp6); like($r, qr/SEE-THIS/, 'tcp6 request'); -like($r, qr/X-PP: 2001:DB8::1/i, 'tcp6 proxy'); -unlike($r, qr/X-IP: 2001:DB8::1/i, 'tcp6 client'); +like($r, qr/X-PP: 2001:DB8::1!123\x0d/i, 'tcp6 proxy'); +unlike($r, qr/X-IP: (2001:DB8::1|[^!]+!123\x0d)/i, 'tcp6 client'); + +$r = pp_get('/t1', $unk1); +like($r, qr/SEE-THIS/, 'unknown request 1'); +like($r, qr/X-PP: !\x0d/, 'unknown proxy 1'); -like(pp_get('/t1', $unk1), qr/SEE-THIS/, 'unknown request 1'); -like(pp_get('/t1', $unk2), qr/SEE-THIS/, 'unknown request 2'); +$r = pp_get('/t1', $unk2); +like($r, qr/SEE-THIS/, 'unknown request 2'); +like($r, qr/X-PP: !\x0d/, 'unknown proxy 2'); # realip $r = pp_get('/pp', $tcp4); like($r, qr/SEE-THIS/, 'tcp4 request realip'); -like($r, qr/X-PP: 192.0.2.1/, 'tcp4 proxy realip'); -like($r, qr/X-IP: 192.0.2.1/, 'tcp4 client realip'); +like($r, qr/X-PP: 192.0.2.1!123\x0d/, 'tcp4 proxy realip'); +like($r, qr/X-IP: 192.0.2.1!123\x0d/, 'tcp4 client realip'); $r = pp_get('/pp', $tcp6); like($r, qr/SEE-THIS/, 'tcp6 request realip'); -like($r, qr/X-PP: 2001:DB8::1/i, 'tcp6 proxy realip'); -like($r, qr/X-IP: 2001:DB8::1/i, 'tcp6 client realip'); +like($r, qr/X-PP: 2001:DB8::1!123\x0d/i, 'tcp6 proxy realip'); +like($r, qr/X-IP: 2001:DB8::1!123\x0d/i, 'tcp6 client realip'); # access @@ -115,9 +124,8 @@ like($r, qr/403 Forbidden/, 'tcp6 access'); $t->stop(); -my $log = $t->read_file('pp.log'); -like($log, qr!^192\.0\.2\.1 GET /pp_4!m, 'tcp4 access log'); -like($log, qr!^2001:DB8::1 GET /pp_6!mi, 'tcp6 access log'); +is($t->read_file('pp4.log'), "192.0.2.1:123\n", 'tcp4 log'); +is($t->read_file('pp6.log'), "2001:db8::1:123\n", 'tcp6 log'); ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_protocol2.t b/tests/nginx-tests/nginx-tests/proxy_protocol2.t index 2342eb6308..084adab5ab 100644 --- a/tests/nginx-tests/nginx-tests/proxy_protocol2.t +++ b/tests/nginx-tests/nginx-tests/proxy_protocol2.t @@ -24,7 +24,7 @@ select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/http access realip/); -$t->write_file_expand('nginx.conf', <<'EOF')->plan(21); +$t->write_file_expand('nginx.conf', <<'EOF')->plan(23); %%TEST_GLOBALS%% @@ -36,28 +36,32 @@ events { http { %%TEST_GLOBALS_HTTP%% - log_format pp '$remote_addr $request'; + log_format pp $remote_addr:$remote_port; server { listen 127.0.0.1:8080 proxy_protocol; server_name localhost; set_real_ip_from 127.0.0.1/32; - add_header X-IP $remote_addr; - add_header X-PP $proxy_protocol_addr; + add_header X-IP $remote_addr!$remote_port; + add_header X-PP $proxy_protocol_addr!$proxy_protocol_port; location /pp { real_ip_header proxy_protocol; error_page 404 =200 /t1; - access_log %%TESTDIR%%/pp.log pp; location /pp_4 { deny 192.0.2.1/32; + access_log %%TESTDIR%%/pp4.log pp; } + location /pp_6 { deny 2001:DB8::1/128; + access_log %%TESTDIR%%/pp6.log pp; } } + + location / { } } } @@ -69,10 +73,10 @@ $t->run(); ############################################################################### my $p = pack("N3C", 0x0D0A0D0A, 0x000D0A51, 0x5549540A, 0x21); -my $tcp4 = $p . pack("CnN2n2", 0x11, 12, 0xc0000201, 0xc0000202, 1234, 5678); +my $tcp4 = $p . pack("CnN2n2", 0x11, 12, 0xc0000201, 0xc0000202, 123, 5678); my $tcp6 = $p . pack("CnNx8NNx8Nn2", 0x21, 36, - 0x20010db8, 0x00000001, 0x20010db8, 0x00000002, 1234, 5678); -my $tlv = $p . pack("CnN2n2x9", 0x11, 21, 0xc0000201, 0xc0000202, 1234, 5678); + 0x20010db8, 0x00000001, 0x20010db8, 0x00000002, 123, 5678); +my $tlv = $p . pack("CnN2n2x9", 0x11, 21, 0xc0000201, 0xc0000202, 123, 5678); my $unk1 = $p . pack("Cxx", 0x01); my $unk2 = $p . pack("CnC4", 0x41, 4, 1, 2, 3, 4); my $r; @@ -81,33 +85,38 @@ my $r; $r = pp_get('/t1', $tcp4); like($r, qr/SEE-THIS/, 'tcp4 request'); -like($r, qr/X-PP: 192.0.2.1/, 'tcp4 proxy'); -unlike($r, qr/X-IP: 192.0.2.1/, 'tcp4 client'); +like($r, qr/X-PP: 192.0.2.1!123\x0d/, 'tcp4 proxy'); +unlike($r, qr/X-IP: (192.0.2.1|[^!]+!123\x0d)/, 'tcp4 client'); $r = pp_get('/t1', $tcp6); like($r, qr/SEE-THIS/, 'tcp6 request'); -like($r, qr/X-PP: 2001:DB8::1/i, 'tcp6 proxy'); -unlike($r, qr/X-IP: 2001:DB8::1/i, 'tcp6 client'); +like($r, qr/X-PP: 2001:DB8::1!123\x0d/i, 'tcp6 proxy'); +unlike($r, qr/X-IP: (2001:DB8::1|[^!]+!123\x0d)/i, 'tcp6 client'); $r = pp_get('/t1', $tlv); like($r, qr/SEE-THIS/, 'tlv request'); -like($r, qr/X-PP: 192.0.2.1/, 'tlv proxy'); -unlike($r, qr/X-IP: 192.0.2.1/, 'tlv client'); +like($r, qr/X-PP: 192.0.2.1!123\x0d/, 'tlv proxy'); +unlike($r, qr/X-IP: (192.0.2.1|[^!]+!123\x0d)/, 'tlv client'); + +$r = pp_get('/t1', $unk1); +like($r, qr/SEE-THIS/, 'unknown request 1'); +like($r, qr/X-PP: !\x0d/, 'unknown proxy 1'); -like(pp_get('/t1', $unk1), qr/SEE-THIS/, 'unknown request 1'); -like(pp_get('/t1', $unk2), qr/SEE-THIS/, 'unknown request 2'); +$r = pp_get('/t1', $unk2); +like($r, qr/SEE-THIS/, 'unknown request 2'); +like($r, qr/X-PP: !\x0d/, 'unknown proxy 2'); # realip $r = pp_get('/pp', $tcp4); like($r, qr/SEE-THIS/, 'tcp4 request realip'); -like($r, qr/X-PP: 192.0.2.1/, 'tcp4 proxy realip'); -like($r, qr/X-IP: 192.0.2.1/, 'tcp4 client realip'); +like($r, qr/X-PP: 192.0.2.1!123\x0d/, 'tcp4 proxy realip'); +like($r, qr/X-IP: 192.0.2.1!123\x0d/, 'tcp4 client realip'); $r = pp_get('/pp', $tcp6); like($r, qr/SEE-THIS/, 'tcp6 request realip'); -like($r, qr/X-PP: 2001:DB8::1/i, 'tcp6 proxy realip'); -like($r, qr/X-IP: 2001:DB8::1/i, 'tcp6 client realip'); +like($r, qr/X-PP: 2001:DB8::1!123\x0d/i, 'tcp6 proxy realip'); +like($r, qr/X-IP: 2001:DB8::1!123\x0d/i, 'tcp6 client realip'); # access @@ -121,9 +130,8 @@ like($r, qr/403 Forbidden/, 'tcp6 access'); $t->stop(); -my $log = $t->read_file('pp.log'); -like($log, qr!^192\.0\.2\.1 GET /pp_4!m, 'tcp4 access log'); -like($log, qr!^2001:DB8::1 GET /pp_6!mi, 'tcp6 access log'); +is($t->read_file('pp4.log'), "192.0.2.1:123\n", 'tcp4 log'); +is($t->read_file('pp6.log'), "2001:db8::1:123\n", 'tcp6 log'); ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_protocol2_server.t b/tests/nginx-tests/nginx-tests/proxy_protocol2_server.t new file mode 100644 index 0000000000..23ddc7ebdf --- /dev/null +++ b/tests/nginx-tests/nginx-tests/proxy_protocol2_server.t @@ -0,0 +1,154 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for haproxy protocol. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http access realip/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + log_format pp $remote_addr:$remote_port; + + add_header X-IP $remote_addr!$remote_port; + add_header X-PP $proxy_protocol_addr!$proxy_protocol_port; + add_header X-PPS $proxy_protocol_server_addr!$proxy_protocol_server_port; + + server { + listen 127.0.0.1:8080 proxy_protocol; + server_name localhost; + + set_real_ip_from 127.0.0.1/32; + + location /pp { + real_ip_header proxy_protocol; + error_page 404 =200 /t1; + + location /pp_4 { + deny 192.0.2.1/32; + access_log %%TESTDIR%%/pp4.log pp; + } + + location /pp_6 { + deny 2001:DB8::1/128; + access_log %%TESTDIR%%/pp6.log pp; + } + } + + location / { } + } +} + +EOF + +$t->write_file('t1', 'SEE-THIS'); +$t->run()->plan(28); + +############################################################################### + +my $p = pack("N3C", 0x0D0A0D0A, 0x000D0A51, 0x5549540A, 0x21); +my $tcp4 = $p . pack("CnN2n2", 0x11, 12, 0xc0000201, 0xc0000202, 123, 567); +my $tcp6 = $p . pack("CnNx8NNx8Nn2", 0x21, 36, + 0x20010db8, 0x00000001, 0x20010db8, 0x00000002, 123, 567); +my $tlv = $p . pack("CnN2n2x9", 0x11, 21, 0xc0000201, 0xc0000202, 123, 567); +my $unk1 = $p . pack("Cxx", 0x01); +my $unk2 = $p . pack("CnC4", 0x41, 4, 1, 2, 3, 4); +my $r; + +# no realip, just PROXY header parsing + +$r = pp_get('/t1', $tcp4); +like($r, qr/SEE-THIS/, 'tcp4 request'); +like($r, qr/X-PP: 192.0.2.1!123\x0d/, 'tcp4 proxy'); +like($r, qr/X-PPS: 192.0.2.2!567\x0d/, 'tcp4 proxy server'); +unlike($r, qr/X-IP: (192.0.2.1|[^!]+!123\x0d)/, 'tcp4 client'); + +$r = pp_get('/t1', $tcp6); +like($r, qr/SEE-THIS/, 'tcp6 request'); +like($r, qr/X-PP: 2001:DB8::1!123\x0d/i, 'tcp6 proxy'); +like($r, qr/X-PPS: 2001:DB8::2!567\x0d/i, 'tcp6 proxy server'); +unlike($r, qr/X-IP: (2001:DB8::1|[^!]+!123\x0d)/i, 'tcp6 client'); + +$r = pp_get('/t1', $tlv); +like($r, qr/SEE-THIS/, 'tlv request'); +like($r, qr/X-PP: 192.0.2.1!123\x0d/, 'tlv proxy'); +like($r, qr/X-PPS: 192.0.2.2!567\x0d/, 'tlv proxy server'); +unlike($r, qr/X-IP: (192.0.2.1|[^!]+!123\x0d)/, 'tlv client'); + +$r = pp_get('/t1', $unk1); +like($r, qr/SEE-THIS/, 'unknown request 1'); +like($r, qr/X-PP: !\x0d/, 'unknown proxy 1'); +like($r, qr/X-PPS: !\x0d/, 'unknown proxy server 1'); + +$r = pp_get('/t1', $unk2); +like($r, qr/SEE-THIS/, 'unknown request 2'); +like($r, qr/X-PP: !\x0d/, 'unknown proxy 2'); +like($r, qr/X-PPS: !\x0d/, 'unknown proxy server 2'); + +# realip + +$r = pp_get('/pp', $tcp4); +like($r, qr/SEE-THIS/, 'tcp4 request realip'); +like($r, qr/X-PP: 192.0.2.1!123\x0d/, 'tcp4 proxy realip'); +like($r, qr/X-IP: 192.0.2.1!123\x0d/, 'tcp4 client realip'); + +$r = pp_get('/pp', $tcp6); +like($r, qr/SEE-THIS/, 'tcp6 request realip'); +like($r, qr/X-PP: 2001:DB8::1!123\x0d/i, 'tcp6 proxy realip'); +like($r, qr/X-IP: 2001:DB8::1!123\x0d/i, 'tcp6 client realip'); + +# access + +$r = pp_get('/pp_4', $tcp4); +like($r, qr/403 Forbidden/, 'tcp4 access'); + +$r = pp_get('/pp_6', $tcp6); +like($r, qr/403 Forbidden/, 'tcp6 access'); + +# client address in access.log + +$t->stop(); + +is($t->read_file('pp4.log'), "192.0.2.1:123\n", 'tcp4 log'); +is($t->read_file('pp6.log'), "2001:db8::1:123\n", 'tcp6 log'); + +############################################################################### + +sub pp_get { + my ($url, $proxy) = @_; + return http($proxy . <new()->has(qw/http access realip/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + log_format pp $remote_addr:$remote_port; + + add_header X-IP $remote_addr!$remote_port; + add_header X-PP $proxy_protocol_addr!$proxy_protocol_port; + add_header X-PPS $proxy_protocol_server_addr!$proxy_protocol_server_port; + + server { + listen 127.0.0.1:8080 proxy_protocol; + server_name localhost; + + set_real_ip_from 127.0.0.1/32; + + location /pp { + real_ip_header proxy_protocol; + error_page 404 =200 /t1; + + location /pp_4 { + deny 192.0.2.1/32; + access_log %%TESTDIR%%/pp4.log pp; + } + + location /pp_6 { + deny 2001:DB8::1/128; + access_log %%TESTDIR%%/pp6.log pp; + } + } + + location / { } + } +} + +EOF + +$t->write_file('t1', 'SEE-THIS'); +$t->run()->plan(24); + +############################################################################### + +my $tcp4 = 'PROXY TCP4 192.0.2.1 192.0.2.2 123 567' . CRLF; +my $tcp6 = 'PROXY TCP6 2001:Db8::1 2001:Db8::2 123 567' . CRLF; +my $unk1 = 'PROXY UNKNOWN' . CRLF; +my $unk2 = 'PROXY UNKNOWN 1 2 3 4 5 6' . CRLF; +my $r; + +# no realip, just PROXY header parsing + +$r = pp_get('/t1', $tcp4); +like($r, qr/SEE-THIS/, 'tcp4 request'); +like($r, qr/X-PP: 192.0.2.1!123\x0d/, 'tcp4 proxy'); +like($r, qr/X-PPS: 192.0.2.2!567\x0d/, 'tcp4 proxy server'); +unlike($r, qr/X-IP: (192.0.2.1|[^!]+!123\x0d)/, 'tcp4 client'); + +$r = pp_get('/t1', $tcp6); +like($r, qr/SEE-THIS/, 'tcp6 request'); +like($r, qr/X-PP: 2001:DB8::1!123\x0d/i, 'tcp6 proxy'); +like($r, qr/X-PPS: 2001:DB8::2!567\x0d/i, 'tcp6 proxy server'); +unlike($r, qr/X-IP: (2001:DB8::1|[^!]+!123\x0d)/i, 'tcp6 client'); + +$r = pp_get('/t1', $unk1); +like($r, qr/SEE-THIS/, 'unknown request 1'); +like($r, qr/X-PP: !\x0d/, 'unknown proxy 1'); +like($r, qr/X-PPS: !\x0d/, 'unknown proxy server 1'); + +$r = pp_get('/t1', $unk2); +like($r, qr/SEE-THIS/, 'unknown request 2'); +like($r, qr/X-PP: !\x0d/, 'unknown proxy 2'); +like($r, qr/X-PPS: !\x0d/, 'unknown proxy server 2'); + +# realip + +$r = pp_get('/pp', $tcp4); +like($r, qr/SEE-THIS/, 'tcp4 request realip'); +like($r, qr/X-PP: 192.0.2.1!123\x0d/, 'tcp4 proxy realip'); +like($r, qr/X-IP: 192.0.2.1!123\x0d/, 'tcp4 client realip'); + +$r = pp_get('/pp', $tcp6); +like($r, qr/SEE-THIS/, 'tcp6 request realip'); +like($r, qr/X-PP: 2001:DB8::1!123\x0d/i, 'tcp6 proxy realip'); +like($r, qr/X-IP: 2001:DB8::1!123\x0d/i, 'tcp6 client realip'); + +# access + +$r = pp_get('/pp_4', $tcp4); +like($r, qr/403 Forbidden/, 'tcp4 access'); + +$r = pp_get('/pp_6', $tcp6); +like($r, qr/403 Forbidden/, 'tcp6 access'); + +# client address in access.log + +$t->stop(); + +is($t->read_file('pp4.log'), "192.0.2.1:123\n", 'tcp4 log'); +is($t->read_file('pp6.log'), "2001:db8::1:123\n", 'tcp6 log'); + +############################################################################### + +sub pp_get { + my ($url, $proxy) = @_; + return http($proxy . <new() + ->has(qw/http realip stream stream_realip stream_return unix/) + ->plan(5); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen unix:%%TESTDIR%%/unix.sock proxy_protocol; + server_name localhost; + + add_header X-IP $remote_addr; + add_header X-PP $proxy_protocol_addr; + real_ip_header proxy_protocol; + + location / { } + location /pp { + set_real_ip_from unix:; + error_page 404 =200 /t; + } + } +} + +stream { + %%TEST_GLOBALS_STREAM%% + + server { + listen unix:%%TESTDIR%%/unix1.sock proxy_protocol; + return $remote_addr:$proxy_protocol_addr; + } + + server { + listen unix:%%TESTDIR%%/unix2.sock proxy_protocol; + return $remote_addr:$proxy_protocol_addr; + + set_real_ip_from unix:; + } + + server { + listen 127.0.0.1:8080; + proxy_pass unix:%%TESTDIR%%/unix.sock; + + proxy_protocol on; + } + + server { + listen 127.0.0.1:8081; + proxy_pass unix:%%TESTDIR%%/unix1.sock; + + proxy_protocol on; + } + + server { + listen 127.0.0.1:8082; + proxy_pass unix:%%TESTDIR%%/unix2.sock; + + proxy_protocol on; + } +} + +EOF + +$t->write_file('t', 'SEE-THIS'); +$t->run(); + +############################################################################### + +my $r = http_get('/t'); +like($r, qr/X-IP: unix/, 'remote_addr'); +like($r, qr/X-PP: 127.0.0.1/, 'proxy_protocol_addr'); + +$r = http_get('/pp'); +like($r, qr/X-IP: 127.0.0.1/, 'remote_addr realip'); + +# listen proxy_protocol in stream + +is(get(8081), 'unix::127.0.0.1', 'stream proxy_protocol'); +is(get(8082), '127.0.0.1:127.0.0.1', 'stream proxy_protocol realip'); + +############################################################################### + +sub get { + Test::Nginx::Stream->new(PeerPort => port(shift))->read(); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_redirect.t b/tests/nginx-tests/nginx-tests/proxy_redirect.t index 482af92568..6078f583fc 100644 --- a/tests/nginx-tests/nginx-tests/proxy_redirect.t +++ b/tests/nginx-tests/nginx-tests/proxy_redirect.t @@ -149,3 +149,5 @@ sub http_get_refresh { http_get($url) =~ /^Refresh:\s(.+?)\x0d?$/mi; return $1; } + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_ssl.t b/tests/nginx-tests/nginx-tests/proxy_ssl.t index 2865fca7d8..92bf153230 100644 --- a/tests/nginx-tests/nginx-tests/proxy_ssl.t +++ b/tests/nginx-tests/nginx-tests/proxy_ssl.t @@ -25,7 +25,7 @@ eval { require IO::Socket::SSL; }; plan(skip_all => 'IO::Socket::SSL not installed') if $@; my $t = Test::Nginx->new()->has(qw/http proxy http_ssl/)->has_daemon('openssl') - ->plan(7)->write_file_expand('nginx.conf', <<'EOF'); + ->plan(8)->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -86,6 +86,7 @@ distinguished_name = req_distinguished_name [ req_distinguished_name ] EOF +$t->write_file('big.html', 'xxxxxxxxxx' x 72000); $t->write_file('index.html', ''); my $d = $t->testdir(); @@ -121,6 +122,9 @@ like(http_get('/timeout'), qr/200 OK/, 'proxy connect timeout'); like(http_get('/timeout_h'), qr/504 Gateway/, 'proxy handshake timeout'); +is(length(Test::Nginx::http_content(http_get('/ssl/big.html'))), 720000, + 'big length'); + ############################################################################### sub http_daemon { diff --git a/tests/nginx-tests/nginx-tests/proxy_ssl_certificate_vars.t b/tests/nginx-tests/nginx-tests/proxy_ssl_certificate_vars.t new file mode 100644 index 0000000000..3b4dbd3ee9 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/proxy_ssl_certificate_vars.t @@ -0,0 +1,148 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http proxy module with variables in ssl certificates. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_ssl proxy/) + ->has_daemon('openssl'); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + proxy_ssl_session_reuse off; + + location / { + proxy_pass https://127.0.0.1:8081/; + proxy_ssl_certificate $arg_cert.example.com.crt; + proxy_ssl_certificate_key $arg_cert.example.com.key; + } + + location /encrypted { + proxy_pass https://127.0.0.1:8082/; + proxy_ssl_certificate $arg_cert.example.com.crt; + proxy_ssl_certificate_key $arg_cert.example.com.key; + proxy_ssl_password_file password; + } + + location /none { + proxy_pass https://127.0.0.1:8082/; + proxy_ssl_certificate $arg_cert; + proxy_ssl_certificate_key $arg_cert; + } + } + + server { + listen 127.0.0.1:8081 ssl; + server_name localhost; + + ssl_certificate 2.example.com.crt; + ssl_certificate_key 2.example.com.key; + + ssl_verify_client optional_no_ca; + ssl_trusted_certificate 1.example.com.crt; + + location / { + add_header X-Verify $ssl_client_verify; + add_header X-Name $ssl_client_s_dn; + } + } + + server { + listen 127.0.0.1:8082 ssl; + server_name localhost; + + ssl_certificate 1.example.com.crt; + ssl_certificate_key 1.example.com.key; + + ssl_verify_client optional_no_ca; + ssl_trusted_certificate 3.example.com.crt; + + location / { + add_header X-Verify $ssl_client_verify; + } + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('1.example.com', '2.example.com') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +foreach my $name ('3.example.com') { + system("openssl genrsa -out $d/$name.key -passout pass:$name " + . "-aes128 2048 >>$d/openssl.out 2>&1") == 0 + or die "Can't create private key: $!\n"; + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt " + . "-key $d/$name.key -passin pass:$name" + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +sleep 1 if $^O eq 'MSWin32'; + +$t->write_file('password', '3.example.com'); +$t->write_file('index.html', ''); + +$t->try_run('no upstream ssl_certificate variables')->plan(4); + +############################################################################### + +like(http_get('/?cert=1'), + qr/X-Verify: SUCCESS/ms, 'variable - verify certificate'); +like(http_get('/?cert=2'), + qr/X-Verify: FAILED/ms, 'variable - fail certificate'); +like(http_get('/encrypted?cert=3'), + qr/X-Verify: SUCCESS/ms, 'variable - with encrypted key'); +like(http_get('/none'), + qr/X-Verify: NONE/ms, 'variable - no certificate'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_ssl_conf_command.t b/tests/nginx-tests/nginx-tests/proxy_ssl_conf_command.t new file mode 100644 index 0000000000..a563665a32 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/proxy_ssl_conf_command.t @@ -0,0 +1,118 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for proxy_ssl_conf_command and friends. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_ssl proxy uwsgi http_v2 grpc/) + ->has_daemon('openssl'); + +$t->{_configure_args} =~ /OpenSSL ([\d\.]+)/; +plan(skip_all => 'OpenSSL too old') unless defined $1 and $1 ge '1.0.2'; +plan(skip_all => 'no ssl_conf_command') if $t->has_module('BoringSSL'); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + proxy_ssl_certificate localhost.crt; + proxy_ssl_certificate_key localhost.key; + proxy_ssl_conf_command Certificate override.crt; + proxy_ssl_conf_command PrivateKey override.key; + proxy_pass https://127.0.0.1:8081; + } + + location /uwsgi { + uwsgi_ssl_certificate localhost.crt; + uwsgi_ssl_certificate_key localhost.key; + uwsgi_ssl_conf_command Certificate override.crt; + uwsgi_ssl_conf_command PrivateKey override.key; + uwsgi_ssl_session_reuse off; + uwsgi_pass suwsgi://127.0.0.1:8081; + } + + location /grpc { + grpc_ssl_certificate localhost.crt; + grpc_ssl_certificate_key localhost.key; + grpc_ssl_conf_command Certificate override.crt; + grpc_ssl_conf_command PrivateKey override.key; + grpc_pass grpcs://127.0.0.1:8082; + } + } + + server { + listen 127.0.0.1:8081 ssl; + listen 127.0.0.1:8082 ssl http2; + server_name localhost; + + ssl_certificate localhost.crt; + ssl_certificate_key localhost.key; + ssl_verify_client optional_no_ca; + + # stub to implement SSL logic for tests + + add_header X-Cert $ssl_client_s_dn always; + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('localhost', 'override') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->write_file('index.html', ''); +$t->run()->plan(3); + +############################################################################### + +like(http_get('/'), qr/CN=override/, 'proxy_ssl_conf_command'); +like(http_get('/uwsgi'), qr/CN=override/, 'uwsgi_ssl_conf_command'); +like(http_get('/grpc'), qr/CN=override/, 'grpc_ssl_conf_command'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_unfinished.t b/tests/nginx-tests/nginx-tests/proxy_unfinished.t index 0fbb543838..6f93ae9a82 100644 --- a/tests/nginx-tests/nginx-tests/proxy_unfinished.t +++ b/tests/nginx-tests/nginx-tests/proxy_unfinished.t @@ -24,7 +24,7 @@ use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; -use Test::Nginx; +use Test::Nginx qw/ :DEFAULT http_content /; ############################################################################### @@ -105,28 +105,28 @@ like(http_get('/cache/chunked'), qr/MISS/, 'unfinished chunked'); # make sure there is no final chunk in unfinished responses -like(http_get_11('/length'), qr/unfinished(?!.*\x0d\x0a?0\x0d\x0a?)/s, +like(http_get_11('/length'), qr/unfinished.*no-last-chunk/s, 'length no final chunk'); -like(http_get_11('/chunked'), qr/unfinished(?!.*\x0d\x0a?0\x0d\x0a?)/s, +like(http_get_11('/chunked'), qr/unfinished.*no-last-chunk/s, 'chunked no final chunk'); # but there is final chunk in complete responses -like(http_get_11('/length/ok'), qr/finished.*\x0d\x0a?0\x0d\x0a?/s, +like(http_get_11('/length/ok'), qr/finished\x0d\x0a$/s, 'length final chunk'); -like(http_get_11('/chunked/ok'), qr/finished.*\x0d\x0a?0\x0d\x0a?/s, +like(http_get_11('/chunked/ok'), qr/finished\x0d\x0a$/s, 'chunked final chunk'); # the same with proxy_buffering set to off -like(http_get_11('/un/length'), qr/unfinished(?!.*\x0d\x0a?0\x0d\x0a?)/s, +like(http_get_11('/un/length'), qr/unfinished.*no-last-chunk/s, 'unbuffered length no final chunk'); -like(http_get_11('/un/chunked'), qr/unfinished(?!.*\x0d\x0a?0\x0d\x0a?)/s, +like(http_get_11('/un/chunked'), qr/unfinished.*no-last-chunk/s, 'unbuffered chunked no final chunk'); -like(http_get_11('/un/length/ok'), qr/finished.*\x0d\x0a?0\x0d\x0a?/s, +like(http_get_11('/un/length/ok'), qr/finished\x0d\x0a$/s, 'unbuffered length final chunk'); -like(http_get_11('/un/chunked/ok'), qr/finished.*\x0d\x0a?0\x0d\x0a?/s, +like(http_get_11('/un/chunked/ok'), qr/finished\x0d\x0a$/s, 'unbuffered chunked final chunk'); # big responses @@ -144,7 +144,7 @@ chmod(0000, $t->testdir() . '/proxy_temp'); my $r = http_get_11('/proxy/big.html', sleep => 0.5); SKIP: { -skip 'finished', 1 if length(Test::Nginx::http_content($r)) == 1024 * 1024 + 8; +skip 'finished', 1 if length($r) == 1024 * 1024 + 8; like($r, qr/X(?!.*\x0d\x0a?0\x0d\x0a?)/s, 'no proxy temp'); @@ -157,12 +157,12 @@ chmod(0700, $t->testdir() . '/proxy_temp'); sub http_get_11 { my ($uri, %extra) = @_; - return http( + return http_content(http( "GET $uri HTTP/1.1" . CRLF . "Connection: close" . CRLF . "Host: localhost" . CRLF . CRLF, %extra - ); + )); } ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_upgrade.t b/tests/nginx-tests/nginx-tests/proxy_upgrade.t index a5db257a4d..83cb15378a 100644 --- a/tests/nginx-tests/nginx-tests/proxy_upgrade.t +++ b/tests/nginx-tests/nginx-tests/proxy_upgrade.t @@ -40,7 +40,7 @@ events { http { %%TEST_GLOBALS_HTTP%% - log_format test "$bytes_sent $body_bytes_sent"; + log_format test "$bytes_sent $body_bytes_sent $sent_http_connection"; access_log %%TESTDIR%%/cc.log test; server { @@ -147,9 +147,9 @@ $t->stop(); open my $f, '<', "$d/cc.log" or die "Can't open cc.log: $!"; -is($f->getline(), shift (@r) . " 540793\n", 'log - bytes'); -is($f->getline(), shift (@r) . " 22\n", 'log - bytes pipelined'); -like($f->getline(), qr/\d+ 0\n/, 'log - bytes noupgrade'); +is($f->getline(), shift (@r) . " 540793 upgrade\n", 'log - bytes'); +is($f->getline(), shift (@r) . " 22 upgrade\n", 'log - bytes pipelined'); +like($f->getline(), qr/\d+ 0 /, 'log - bytes noupgrade'); ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/proxy_variables.t b/tests/nginx-tests/nginx-tests/proxy_variables.t index ab073e8d85..f275fd98a0 100644 --- a/tests/nginx-tests/nginx-tests/proxy_variables.t +++ b/tests/nginx-tests/nginx-tests/proxy_variables.t @@ -22,7 +22,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http proxy/) +my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(4) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -52,7 +52,7 @@ http { EOF $t->run_daemon(\&http_daemon, port(8081)); -$t->try_run('upstream_bytes_sent')->plan(4); +$t->run(); $t->waitforsocket('127.0.0.1:' . port(8081)); diff --git a/tests/nginx-tests/nginx-tests/request_id.t b/tests/nginx-tests/nginx-tests/request_id.t new file mode 100644 index 0000000000..35be946e9f --- /dev/null +++ b/tests/nginx-tests/nginx-tests/request_id.t @@ -0,0 +1,110 @@ +#!/usr/bin/perl + +# (C) Andrey Zelenkov +# (C) Nginx, Inc. + +# Tests for request_id variable. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite ssi/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + log_format id $request_id; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + add_header X-Request-Id $request_id; + add_header X-blah blah; + + location / { + ssi on; + } + location /body { + return 200 $request_id; + } + location /log { + access_log %%TESTDIR%%/id.log id; + return 200; + } + } +} + +EOF + +$t->write_file('index.html', ''); +$t->write_file('add.html', ''); +$t->run()->plan(12); + +############################################################################### + +my ($id1) = http_get('/') =~ qr/^X-Request-Id: (.*)\x0d/m; +my ($id2) = http_get('/') =~ qr/^X-Request-Id: (.*)\x0d/m; + +like($id1, qr/^[a-z0-9]{32}$/, 'format id 1'); +like($id2, qr/^[a-z0-9]{32}$/, 'format id 2'); + +isnt($id1, $id2, 'different id'); + +# same request + +($id1, $id2) = http_get('/body') + =~ qr/^X-Request-Id: (.*?)\x0d.*\x0d\x0a(.*)/ms; + +like($id1, qr/^[a-z0-9]{32}$/, 'format id 1 - same'); +like($id2, qr/^[a-z0-9]{32}$/, 'format id 2 - same'); + +is($id1, $id2, 'equal id - same'); + +# subrequest + +($id1, $id2) = http_get('/add.html') + =~ qr/^X-Request-Id: (.*?)\x0d.*\x0d\x0a(.*)/ms; + +like($id1, qr/^[a-z0-9]{32}$/, 'format id 1 - sub'); +like($id2, qr/^[a-z0-9]{32}$/, 'format id 2 - sub'); + +is($id1, $id2, 'equal id - sub'); + +# log + +($id1) = http_get('/log') =~ qr/^X-Request-Id: (.*)\x0d/m; + +$t->stop(); + +$id2 = $t->read_file('/id.log'); +chomp $id2; + +like($id1, qr/^[a-z0-9]{32}$/, 'format id 1 - log'); +like($id2, qr/^[a-z0-9]{32}$/, 'format id 2 - log'); + +is($id1, $id2, 'equal id - log'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/rewrite.t b/tests/nginx-tests/nginx-tests/rewrite.t index f56c1e309d..31b1a6f62a 100755 --- a/tests/nginx-tests/nginx-tests/rewrite.t +++ b/tests/nginx-tests/nginx-tests/rewrite.t @@ -21,7 +21,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http rewrite/)->plan(22) +my $t = Test::Nginx->new()->has(qw/http rewrite proxy/)->plan(23) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -130,6 +130,12 @@ http { rewrite ^(.*) $1?c=$1; return 200 "uri:$uri args:$args"; } + + location /break { + rewrite ^ /return200; + break; + proxy_pass http://127.0.0.1:8080/return204; + } } } @@ -161,13 +167,8 @@ like(http_get('/return405'), qr!HTTP/1.1 405.*body!ms, 'return 405'); # this used to result in 404, but was changed in 1.15.4 # to respond with 405 instead, much like a real error would do -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.15.4'); - like(http_get('/error404return405'), qr!HTTP/1.1 405!, 'error 404 return 405'); -} - # status code should be 405, and entity body is expected (vs. normal 204 # replies which doesn't expect to have body); use HTTP/1.1 for test # to make problem clear @@ -236,4 +237,8 @@ like(http_get('/capturedup/%25?a=b'), qr!^uri:/capturedup/% args:c=/capturedup/%25&a=b$!ms, 'escape with added args'); +# break + +like(http_get('/break'), qr/200/, 'valid_location reset'); + ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/scgi.t b/tests/nginx-tests/nginx-tests/scgi.t index 66a93f0e60..7d66fa3b8d 100644 --- a/tests/nginx-tests/nginx-tests/scgi.t +++ b/tests/nginx-tests/nginx-tests/scgi.t @@ -24,7 +24,7 @@ select STDOUT; $| = 1; eval { require SCGI; }; plan(skip_all => 'SCGI not installed') if $@; -my $t = Test::Nginx->new()->has(qw/http scgi/)->plan(7) +my $t = Test::Nginx->new()->has(qw/http scgi/)->plan(10) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -81,6 +81,33 @@ like(http_get('/var?b=127.0.0.1:' . port(8081)), qr/SEE-THIS/, 'scgi with variables'); like(http_get('/var?b=u'), qr/SEE-THIS/, 'scgi with variables to upstream'); +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.23.0'); + +my $r = http(<env->{HTTP_X_FORWARDED_FOR} || ''; + my $cookie = $request->env->{HTTP_COOKIE} || ''; + my $foo = $request->env->{HTTP_FOO} || ''; + $request->connection()->print(< 'SCGI not installed') if $@; + +my $t = Test::Nginx->new() + ->has(qw/http scgi cache rewrite addition/)->plan(22) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + scgi_param SCGI 1; + scgi_param REQUEST_URI $request_uri; + scgi_param REQUEST_METHOD $request_method; + + scgi_cache_path cache keys_zone=one:1m; + scgi_cache_key $request_uri; + scgi_cache_valid any 1m; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + scgi_pass 127.0.0.1:8081; + add_after_body /after; + } + + location /unbuf/ { + scgi_pass 127.0.0.1:8081; + scgi_buffering off; + add_after_body /after; + } + + location /head/ { + scgi_pass 127.0.0.1:8081; + scgi_cache one; + add_after_body /after; + } + + location /after { + return 200 ":after\n"; + } + } +} + +EOF + +$t->run_daemon(\&scgi_daemon); +$t->run()->waitforsocket('127.0.0.1:' . port(8081)); + +############################################################################### + +like(http_get('/'), qr/SEE-THIS(?!-BUT-NOT-THIS)/, 'response with extra data'); +like(http_get('/short'), qr/SEE-THIS(?!.*:after)/s, 'too short response'); +like(http_get('/empty'), qr/200 OK(?!.*:after)/s, 'empty too short response'); + +like(http_head('/'), qr/200 OK(?!.*SEE-THIS)/s, 'no data in HEAD'); +like(http_head('/short'), qr/200 OK(?!.*SEE-THIS)/s, 'too short to HEAD'); +like(http_head('/empty'), qr/200 OK/, 'empty response to HEAD'); + +# unbuffered responses + +like(http_get('/unbuf/'), qr/SEE-THIS(?!-BUT-NOT-THIS)/, + 'unbuffered with extra data'); +like(http_get('/unbuf/short'), qr/SEE-THIS(?!.*:after)/s, + 'unbuffered too short response'); +like(http_get('/unbuf/empty'), qr/200 OK(?!.*:after)/s, + 'unbuffered empty too short response'); + +like(http_head('/unbuf/'), qr/200 OK(?!.*SEE-THIS)/s, + 'unbuffered no data in HEAD'); +like(http_head('/unbuf/short'), qr/200 OK(?!.*SEE-THIS)/s, + 'unbuffered too short response to HEAD'); +like(http_head('/unbuf/empty'), qr/200 OK/, + 'unbuffered empty response to HEAD'); + +# caching of responsses to HEAD requests + +like(http_head('/head/empty'), qr/200 OK(?!.*SEE-THIS)/s, 'head no body'); +like(http_head('/head/matching'), qr/200 OK(?!.*SEE-THIS)/s, 'head matching'); +like(http_head('/head/extra'), qr/200 OK(?!.*SEE-THIS)/s, 'head extra'); +like(http_head('/head/short'), qr/200 OK(?!.*SEE-THIS)/s, 'head too short'); + +like(http_get('/head/empty'), qr/SEE-THIS/, 'head no body cached'); +like(http_get('/head/matching'), qr/SEE-THIS/, 'head matching cached'); +like(http_get('/head/extra'), qr/SEE-THIS(?!-BUT-NOT-THIS)/s, + 'head extra cached'); +like(http_get('/head/short'), qr/SEE-THIS(?!.*:after)/s, + 'head too short cached'); + +# "zero size buf" alerts (ticket #2117) + +like(http_get('/zero'), qr/200 OK(?!.*NOT-THIS)/s, 'zero size'); +like(http_get('/unbuf/zero'), qr/200 OK(?!.*NOT-THIS)/s, + 'unbuffered zero size'); + +############################################################################### + +sub scgi_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => '127.0.0.1:' . port(8081), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + my $scgi = SCGI->new($server, blocking => 1); + my ($c, $uri, $head); + + while (my $request = $scgi->accept()) { + eval { $request->read_env(); }; + next if $@; + + $uri = $request->env->{REQUEST_URI}; + $uri =~ s!^/unbuf!!; + + $head = $request->env->{REQUEST_METHOD} eq 'HEAD'; + + $c = $request->connection(); + + if ($uri eq '/') { + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 8\n\n"); + $c->print("SEE-THIS-BUT-NOT-THIS\n"); + + } elsif ($uri eq '/zero') { + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 0\n\n"); + $c->print("NOT-THIS\n"); + + } elsif ($uri eq '/short') { + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 100\n\n"); + $c->print("SEE-THIS-TOO-SHORT-RESPONSE\n"); + + } elsif ($uri eq '/empty') { + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 100\n\n"); + + } elsif ($uri eq '/head/empty') { + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 8\n\n"); + $c->print("SEE-THIS") unless $head; + + } elsif ($uri eq '/head/matching') { + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 8\n\n"); + $c->print("SEE-THIS"); + + } elsif ($uri eq '/head/extra') { + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 8\n\n"); + $c->print("SEE-THIS-BUT-NOT-THIS\n"); + + } elsif ($uri eq '/head/short') { + $c->print("Content-Type: text/html\n"); + $c->print("Content-Length: 100\n\n"); + $c->print("SEE-THIS\n"); + } + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/slice.t b/tests/nginx-tests/nginx-tests/slice.t index 3bab3bc917..26dba3fd80 100644 --- a/tests/nginx-tests/nginx-tests/slice.t +++ b/tests/nginx-tests/nginx-tests/slice.t @@ -15,7 +15,7 @@ use Test::More; BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; -use Test::Nginx qw/ :DEFAULT http_end /; +use Test::Nginx; ############################################################################### @@ -23,7 +23,7 @@ select STDERR; $| = 1; select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/http proxy cache fastcgi slice rewrite/) - ->plan(76); + ->plan(79); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -109,6 +109,8 @@ http { listen 127.0.0.1:8081; server_name localhost; + add_header Accept-Ranges bytes; + location / { if ($http_range = "") { set $limit_rate 100; @@ -129,6 +131,11 @@ my $r; like(http_get('/cache/nx'), qr/ 404 /, 'not found'); like(http_get('/cache/t'), qr/ 200 .*0123456789abcdef$/ms, 'no range'); +$r = get('/proxy/t', 'Range: bytes=3-4'); +like($r, qr/ 206 /, 'proxy - 206 partial reply'); +like($r, qr/^34$/m, 'proxy - correct content'); +unlike($r, qr/Accept-Ranges/, 'proxy - no original accept-ranges'); + $r = get('/cache/t?single', "Range: bytes=0-0"); like($r, qr/ 206 /, 'single - 206 partial reply'); like($r, qr/^0$/m, 'single - correct content'); diff --git a/tests/nginx-tests/nginx-tests/ssi.t b/tests/nginx-tests/nginx-tests/ssi.t index 9f696dcb02..621af74d45 100644 --- a/tests/nginx-tests/nginx-tests/ssi.t +++ b/tests/nginx-tests/nginx-tests/ssi.t @@ -22,7 +22,7 @@ select STDERR; $| = 1; select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/http ssi cache proxy rewrite/) - ->plan(27); + ->plan(30); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -90,6 +90,15 @@ $t->write_file('test2.html', 'XX'); $t->write_file('test3.html', 'XX'); +$t->write_file('test4-echo-none.html', + 'X' + . 'X'); +$t->write_file('test5-echo-url.html', + 'X' + . 'X'); +$t->write_file('test6-echo-entity.html', + 'X' + . 'X'); $t->write_file('test-args-rewrite.html', 'XX'); $t->write_file('test-empty1.html', 'XX'); @@ -136,6 +145,20 @@ like(http_get('/test2.html'), qr/^XXtestXX$/m, 'argument via include'); like(http_get('/test3.html'), qr/^XtestX$/m, 'set'); +like(http_get('/test4-echo-none.html'), qr/^XX$/m, + 'echo encoding none'); + +TODO: { +local $TODO = 'no strict URI escaping yet' unless $t->has_version('1.21.1'); + +like(http_get('/test5-echo-url.html'), qr/^X%3Ctest%3EX$/m, + 'echo encoding url'); + +} + +like(http_get('/test6-echo-entity.html'), qr/^X<test>X$/m, + 'echo encoding entity'); + # args should be in subrequest even if original request has no args and that # was queried somehow (e.g. by server rewrites) diff --git a/tests/nginx-tests/nginx-tests/ssl.t b/tests/nginx-tests/nginx-tests/ssl.t index 556c4cd3ac..7f7c550a5a 100644 --- a/tests/nginx-tests/nginx-tests/ssl.t +++ b/tests/nginx-tests/nginx-tests/ssl.t @@ -31,7 +31,7 @@ eval { IO::Socket::SSL::SSL_VERIFY_NONE(); }; plan(skip_all => 'IO::Socket::SSL too old') if $@; my $t = Test::Nginx->new()->has(qw/http http_ssl rewrite proxy/) - ->has_daemon('openssl')->plan(23); + ->has_daemon('openssl')->plan(28); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -51,6 +51,8 @@ http { ssl_certificate localhost.crt; ssl_session_tickets off; + log_format ssl $ssl_protocol; + server { listen 127.0.0.1:8085 ssl; listen 127.0.0.1:8080; @@ -61,7 +63,9 @@ http { ssl_session_cache shared:SSL:1m; ssl_verify_client optional_no_ca; - location /reuse { + keepalive_requests 1000; + + location / { return 200 "body $ssl_session_reused"; } location /id { @@ -70,6 +74,9 @@ http { location /cipher { return 200 "body $ssl_cipher"; } + location /ciphers { + return 200 "body $ssl_ciphers"; + } location /client_verify { return 200 "body $ssl_client_verify"; } @@ -89,6 +96,8 @@ http { location /body { add_header X-Body $request_body always; proxy_pass http://127.0.0.1:8080/; + + access_log %%TESTDIR%%/ssl.log ssl; } } @@ -100,7 +109,6 @@ http { ssl on; ssl_session_cache builtin; - ssl_session_timeout 1; location / { return 200 "body $ssl_session_reused"; @@ -138,8 +146,17 @@ http { location / { return 200 "body $ssl_session_reused"; } - location /ciphers { - return 200 "body $ssl_ciphers"; + } + + server { + listen 127.0.0.1:8086 ssl; + server_name localhost; + + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 1; + + location / { + return 200 "body $ssl_session_reused"; } } } @@ -201,9 +218,7 @@ foreach my $name ('localhost', 'inner') { or die "Can't create certificate for $name: $!\n"; } -my $ctx = new IO::Socket::SSL::SSL_Context( - SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), - SSL_session_cache_size => 100); +# suppress deprecation warning open OLDERR, ">&", \*STDERR; close STDERR; $t->run(); @@ -211,46 +226,73 @@ open STDERR, ">&", \*OLDERR; ############################################################################### -like(get('/reuse', 8085), qr/^body \.$/m, 'shared initial session'); -like(get('/reuse', 8085), qr/^body r$/m, 'shared session reused'); +my $ctx; + +SKIP: { +skip 'no TLS 1.3 sessions', 6 if get('/protocol', 8085) =~ /TLSv1.3/ + && ($Net::SSLeay::VERSION < 1.88 || $IO::Socket::SSL::VERSION < 2.061); -like(get('/', 8081), qr/^body \.$/m, 'builtin initial session'); -like(get('/', 8081), qr/^body r$/m, 'builtin session reused'); +$ctx = get_ssl_context(); -like(get('/', 8082), qr/^body \.$/m, 'builtin size initial session'); -like(get('/', 8082), qr/^body r$/m, 'builtin size session reused'); +like(get('/', 8085, $ctx), qr/^body \.$/m, 'cache shared'); +like(get('/', 8085, $ctx), qr/^body r$/m, 'cache shared reused'); + +$ctx = get_ssl_context(); + +like(get('/', 8081, $ctx), qr/^body \.$/m, 'cache builtin'); +like(get('/', 8081, $ctx), qr/^body r$/m, 'cache builtin reused'); + +$ctx = get_ssl_context(); + +like(get('/', 8082, $ctx), qr/^body \.$/m, 'cache builtin size'); +like(get('/', 8082, $ctx), qr/^body r$/m, 'cache builtin size reused'); + +} -like(get('/', 8083), qr/^body \.$/m, 'reused none initial session'); -like(get('/', 8083), qr/^body \.$/m, 'session not reused 1'); +$ctx = get_ssl_context(); -like(get('/', 8084), qr/^body \.$/m, 'reused off initial session'); -like(get('/', 8084), qr/^body \.$/m, 'session not reused 2'); +like(get('/', 8083, $ctx), qr/^body \.$/m, 'cache none'); +like(get('/', 8083, $ctx), qr/^body \.$/m, 'cache none not reused'); + +$ctx = get_ssl_context(); + +like(get('/', 8084, $ctx), qr/^body \.$/m, 'cache off'); +like(get('/', 8084, $ctx), qr/^body \.$/m, 'cache off not reused'); # ssl certificate inheritance -my $s = get_ssl_socket($ctx, port(8081)); +my $s = get_ssl_socket(8081); like($s->dump_peer_certificate(), qr/CN=localhost/, 'CN'); $s->close(); -$s = get_ssl_socket($ctx, port(8085)); +$s = get_ssl_socket(8085); like($s->dump_peer_certificate(), qr/CN=inner/, 'CN inner'); $s->close(); # session timeout +$ctx = get_ssl_context(); + +get('/', 8086, $ctx); select undef, undef, undef, 2.1; -like(get('/', 8081), qr/^body \.$/m, 'session timeout'); +like(get('/', 8086, $ctx), qr/^body \.$/m, 'session timeout'); # embedded variables like(get('/id', 8085), qr/^body \w{64}$/m, 'session id'); unlike(http_get('/id'), qr/body \w/, 'session id no ssl'); like(get('/cipher', 8085), qr/^body [\w-]+$/m, 'cipher'); -my $re = $t->has_module('BoringSSL') ? '' : qr/[:\w-]+/; -like(get('/ciphers', 8084), qr/^body $re$/m, 'ciphers'); + +SKIP: { +skip 'BoringSSL', 1 if $t->has_module('BoringSSL'); + +like(get('/ciphers', 8085), qr/^body [:\w-]+$/m, 'ciphers'); + +} + like(get('/client_verify', 8085), qr/^body NONE$/m, 'client verify'); like(get('/protocol', 8085), qr/^body (TLS|SSL)v(\d|\.)+$/m, 'protocol'); like(cert('/issuer', 8085), qr!^body CN=issuer:/CN=issuer$!m, 'issuer'); @@ -262,11 +304,40 @@ like(cert('/time', 8085), qr/^body [:\s\w]+![:\s\w]+![23]$/m, 'time'); like(get_body('/body', '0123456789', 20, 5), qr/X-Body: (0123456789){100}/, 'request body chunked'); +# pipelined requests + +$s = get_ssl_socket(8085); +my $req = < $s) || ""; +is(() = $r =~ /(200 OK)/g, 1000, 'pipelined requests'); + +# OpenSSL 3.0 error "unexpected eof while reading" seen as a critical error + +ok(get_ssl_socket(8085), 'ssl unexpected eof'); + +# close_notify is sent before lingering close + +is(get_ssl_shutdown(8085), 1, 'ssl shutdown on lingering close'); + +$t->stop(); + +like($t->read_file('ssl.log'), qr/^(TLS|SSL)v(\d|\.)+$/m, + 'log ssl variable on lingering close'); + +like(`grep -F '[crit]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no crit'); + ############################################################################### sub get { - my ($uri, $port) = @_; - my $s = get_ssl_socket($ctx, port($port)) or return; + my ($uri, $port, $ctx) = @_; + my $s = get_ssl_socket($port, $ctx) or return; my $r = http_get($uri, socket => $s); $s->close(); return $r; @@ -274,13 +345,14 @@ sub get { sub get_body { my ($uri, $body, $len, $n) = @_; - my $s = get_ssl_socket($ctx, port(8085)) or return; + my $s = get_ssl_socket(8085) or return; http("GET /body HTTP/1.1" . CRLF . "Host: localhost" . CRLF . "Connection: close" . CRLF . "Transfer-Encoding: chunked" . CRLF . CRLF, socket => $s, start => 1); - http("c8" . CRLF . $body x $len . CRLF, socket => $s, start => 1) + my $chs = unpack("H*", pack("C", length($body) * $len)); + http($chs . CRLF . $body x $len . CRLF, socket => $s, start => 1) for 1 .. $n; my $r = http("0" . CRLF . CRLF, socket => $s); $s->close(); @@ -289,14 +361,21 @@ sub get_body { sub cert { my ($uri, $port) = @_; - my $s = get_ssl_socket(undef, port($port), + my $s = get_ssl_socket($port, undef, SSL_cert_file => "$d/subject.crt", SSL_key_file => "$d/subject.key") or return; http_get($uri, socket => $s); } +sub get_ssl_context { + return IO::Socket::SSL::SSL_Context->new( + SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), + SSL_session_cache_size => 100 + ); +} + sub get_ssl_socket { - my ($ctx, $port, %extra) = @_; + my ($port, $ctx, %extra) = @_; my $s; eval { @@ -306,7 +385,7 @@ sub get_ssl_socket { $s = IO::Socket::SSL->new( Proto => 'tcp', PeerAddr => '127.0.0.1', - PeerPort => $port, + PeerPort => port($port), SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), SSL_reuse_ctx => $ctx, SSL_error_trap => sub { die $_[1] }, @@ -324,4 +403,18 @@ sub get_ssl_socket { return $s; } +sub get_ssl_shutdown { + my ($port) = @_; + + my $s = IO::Socket::INET->new('127.0.0.1:' . port($port)); + my $ctx = Net::SSLeay::CTX_new() or die("Failed to create SSL_CTX $!"); + my $ssl = Net::SSLeay::new($ctx) or die("Failed to create SSL $!"); + Net::SSLeay::set_fd($ssl, fileno($s)); + Net::SSLeay::connect($ssl) or die("ssl connect"); + Net::SSLeay::write($ssl, 'GET /' . CRLF . 'extra'); + Net::SSLeay::read($ssl); + Net::SSLeay::set_shutdown($ssl, 1); + Net::SSLeay::shutdown($ssl); +} + ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/ssl_certificate.t b/tests/nginx-tests/nginx-tests/ssl_certificate.t new file mode 100644 index 0000000000..ff49eeed24 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/ssl_certificate.t @@ -0,0 +1,231 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http ssl module with dynamic certificates. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { + require Net::SSLeay; + Net::SSLeay::load_error_strings(); + Net::SSLeay::SSLeay_add_ssl_algorithms(); + Net::SSLeay::randomize(); +}; +plan(skip_all => 'Net::SSLeay not installed') if $@; + +eval { + my $ctx = Net::SSLeay::CTX_new() or die; + my $ssl = Net::SSLeay::new($ctx) or die; + Net::SSLeay::set_tlsext_host_name($ssl, 'example.org') == 1 or die; +}; +plan(skip_all => 'Net::SSLeay with OpenSSL SNI support required') if $@; + +my $t = Test::Nginx->new()->has(qw/http http_ssl geo/) + ->has_daemon('openssl'); + +$t->{_configure_args} =~ /OpenSSL ([\d\.]+)/; +plan(skip_all => 'OpenSSL too old') unless defined $1 and $1 ge '1.0.2'; + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + geo $one { + default one; + } + + geo $two { + default two; + } + + geo $pass { + default pass; + } + + add_header X-SSL $ssl_server_name:$ssl_session_reused; + ssl_session_cache shared:SSL:1m; + ssl_session_tickets off; + + server { + listen 127.0.0.1:8080 ssl; + server_name default; + + ssl_certificate $one.crt; + ssl_certificate_key $one.key; + } + + server { + listen 127.0.0.1:8080 ssl; + server_name virtual; + + # found in key + ssl_certificate $two.crt; + ssl_certificate_key $two.key; + } + + server { + listen 127.0.0.1:8080 ssl; + server_name no_ctx; + } + + server { + listen 127.0.0.1:8083 ssl; + server_name password; + + # found in key + ssl_certificate pass.crt; + ssl_certificate_key $pass.key; + ssl_password_file password_file; + } + + server { + listen 127.0.0.1:8081 ssl; + server_name default; + + ssl_certificate $one.crt; + ssl_certificate_key $one.key; + } + + server { + listen 127.0.0.1:8082 ssl; + server_name default; + + ssl_certificate $two.crt; + ssl_certificate_key $two.key; + } + + server { + listen 127.0.0.1:8084 ssl; + server_name localhost; + + ssl_certificate $ssl_server_name.crt; + ssl_certificate_key $ssl_server_name.key; + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('one', 'two') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +foreach my $name ('pass') { + system("openssl genrsa -out $d/$name.key -passout pass:pass " + . "-aes128 2048 >>$d/openssl.out 2>&1") == 0 + or die "Can't create $name key: $!\n"; + system("openssl req -x509 -new -config $d/openssl.conf " + . "-subj /CN=$name/ -out $d/$name.crt -key $d/$name.key " + . "-passin pass:pass >>$d/openssl.out 2>&1") == 0 + or die "Can't create $name certificate: $!\n"; +} + +$t->write_file('password_file', 'pass'); +$t->write_file('index.html', ''); + +$t->run()->plan(11); + +############################################################################### + +like(cert('default', 8080), qr/CN=one/, 'default certificate'); +like(get('default', 8080), qr/default/, 'default context'); + +like(cert('virtual', 8080), qr/CN=two/, 'virtual server certificate'); +like(get('virtual', 8080), qr/virtual/, 'virtual server context'); + +like(cert('no_ctx', 8080), qr/CN=one/, 'certificate - no context'); +like(get('no_ctx', 8080), qr/no_ctx/, 'virtual server - no context'); + +like(get('password', 8083), qr/password/, 'ssl_password_file'); + +# session reuse + +my ($s, $ssl) = get('default', 8080); +my $ses = Net::SSLeay::get_session($ssl); + +like(get('default', 8080, $ses), qr/default:r/, 'session reused'); +like(get('default', 8081, $ses), qr/default:r/, 'session id context match'); +like(get('default', 8082, $ses), qr/default:\./, 'session id context distinct'); + +# errors + +Net::SSLeay::ERR_clear_error(); +get_ssl_socket('nx', 8084); +ok(Net::SSLeay::ERR_peek_error(), 'no certificate'); + +############################################################################### + +sub get { + my ($host, $port, $ctx) = @_; + my ($s, $ssl) = get_ssl_socket($host, $port, $ctx) or return; + + local $SIG{PIPE} = 'IGNORE'; + + Net::SSLeay::write($ssl, 'GET / HTTP/1.0' . CRLF . CRLF); + my $r = Net::SSLeay::read($ssl); + Net::SSLeay::shutdown($ssl); + $s->close(); + return $r unless wantarray(); + return ($s, $ssl); +} + +sub cert { + my ($host, $port, $ctx) = @_; + my ($s, $ssl) = get_ssl_socket($host, $port, $ctx) or return; + Net::SSLeay::dump_peer_certificate($ssl); +} + +sub get_ssl_socket { + my ($host, $port, $ses) = @_; + + my $s = IO::Socket::INET->new('127.0.0.1:' . port($port)); + my $ctx = Net::SSLeay::CTX_new() or die("Failed to create SSL_CTX $!"); + my $ssl = Net::SSLeay::new($ctx) or die("Failed to create SSL $!"); + Net::SSLeay::set_tlsext_host_name($ssl, $host); + Net::SSLeay::set_session($ssl, $ses) if defined $ses; + Net::SSLeay::set_fd($ssl, fileno($s)); + Net::SSLeay::connect($ssl); + return ($s, $ssl); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/ssl_certificate_perl.t b/tests/nginx-tests/nginx-tests/ssl_certificate_perl.t new file mode 100644 index 0000000000..35d8469d43 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/ssl_certificate_perl.t @@ -0,0 +1,125 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http ssl module, loading certificates from memory with perl module. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { + require Net::SSLeay; + Net::SSLeay::load_error_strings(); + Net::SSLeay::SSLeay_add_ssl_algorithms(); + Net::SSLeay::randomize(); +}; +plan(skip_all => 'Net::SSLeay not installed') if $@; + +eval { + my $ctx = Net::SSLeay::CTX_new() or die; + my $ssl = Net::SSLeay::new($ctx) or die; + Net::SSLeay::set_tlsext_host_name($ssl, 'example.org') == 1 or die; +}; +plan(skip_all => 'Net::SSLeay with OpenSSL SNI support required') if $@; + +my $t = Test::Nginx->new()->has(qw/http http_ssl perl/)->has_daemon('openssl'); + +$t->{_configure_args} =~ /OpenSSL ([\d\.]+)/; +plan(skip_all => 'OpenSSL too old') unless defined $1 and $1 ge '1.0.2'; + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + perl_set $pem ' + sub { + my $r = shift; + local $/; + my $sni = $r->variable("ssl_server_name"); + open my $fh, "<", "%%TESTDIR%%/$sni.crt"; + my $content = <$fh>; + close $fh; + return $content; + } + '; + + server { + listen 127.0.0.1:8080 ssl; + server_name localhost; + + ssl_certificate data:$pem; + ssl_certificate_key data:$pem; + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('one', 'two') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->run()->plan(2); + +############################################################################### + +like(cert('one', 8080), qr/CN=one/, 'certificate'); +like(cert('two', 8080), qr/CN=two/, 'certificate 2'); + +############################################################################### + +sub cert { + my ($host, $port) = @_; + my ($s, $ssl) = get_ssl_socket($host, $port) or return; + Net::SSLeay::dump_peer_certificate($ssl); +} + +sub get_ssl_socket { + my ($host, $port) = @_; + + my $s = IO::Socket::INET->new('127.0.0.1:' . port($port)); + my $ctx = Net::SSLeay::CTX_new() or die("Failed to create SSL_CTX $!"); + my $ssl = Net::SSLeay::new($ctx) or die("Failed to create SSL $!"); + Net::SSLeay::set_tlsext_host_name($ssl, $host); + Net::SSLeay::set_fd($ssl, fileno($s)); + Net::SSLeay::connect($ssl) or die("ssl connect"); + return ($s, $ssl); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/ssl_conf_command.t b/tests/nginx-tests/nginx-tests/ssl_conf_command.t new file mode 100644 index 0000000000..4a79a6de62 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/ssl_conf_command.t @@ -0,0 +1,124 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http ssl module, ssl_conf_command. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { + require Net::SSLeay; + Net::SSLeay::load_error_strings(); + Net::SSLeay::SSLeay_add_ssl_algorithms(); + Net::SSLeay::randomize(); +}; +plan(skip_all => 'Net::SSLeay not installed') if $@; + +my $t = Test::Nginx->new()->has(qw/http http_ssl/) + ->has_daemon('openssl'); + +$t->{_configure_args} =~ /OpenSSL ([\d\.]+)/; +plan(skip_all => 'OpenSSL too old') unless defined $1 and $1 ge '1.0.2'; +plan(skip_all => 'no ssl_conf_command') if $t->has_module('BoringSSL'); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8443 ssl; + server_name localhost; + + ssl_protocols TLSv1.2; + + ssl_session_tickets off; + ssl_conf_command Options SessionTicket; + + ssl_prefer_server_ciphers on; + ssl_conf_command Options -ServerPreference; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256; + + ssl_certificate localhost.crt; + ssl_certificate_key localhost.key; + ssl_conf_command Certificate override.crt; + ssl_conf_command PrivateKey override.key; + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('localhost', 'override') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->run()->plan(3); + +############################################################################### + +my $ctx = Net::SSLeay::CTX_new() or die("Failed to create SSL_CTX $!"); + +my ($s, $ssl) = get_ssl_socket(); +like(Net::SSLeay::dump_peer_certificate($ssl), qr/CN=override/, 'Certificate'); + +my $ses = Net::SSLeay::get_session($ssl); +($s, $ssl) = get_ssl_socket(ses => $ses); +ok(Net::SSLeay::session_reused($ssl), 'SessionTicket'); + +($s, $ssl) = get_ssl_socket(ciphers => + 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384'); +is(Net::SSLeay::get_cipher($ssl), + 'ECDHE-RSA-AES128-GCM-SHA256', 'ServerPreference'); + +############################################################################### + +sub get_ssl_socket { + my (%extra) = @_; + + my $s = IO::Socket::INET->new('127.0.0.1:' . port(8443)); + my $ssl = Net::SSLeay::new($ctx) or die("Failed to create SSL $!"); + Net::SSLeay::set_session($ssl, $extra{ses}) if $extra{ses}; + Net::SSLeay::set_cipher_list($ssl, $extra{ciphers}) if $extra{ciphers}; + Net::SSLeay::set_fd($ssl, fileno($s)); + Net::SSLeay::connect($ssl) or die("ssl connect"); + return ($s, $ssl); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/ssl_curve.t b/tests/nginx-tests/nginx-tests/ssl_curve.t new file mode 100644 index 0000000000..3b6d27dc7b --- /dev/null +++ b/tests/nginx-tests/nginx-tests/ssl_curve.t @@ -0,0 +1,124 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http ssl module, $ssl_curve variable. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { require IO::Socket::SSL; }; +plan(skip_all => 'IO::Socket::SSL not installed') if $@; +eval { IO::Socket::SSL::SSL_VERIFY_NONE(); }; +plan(skip_all => 'IO::Socket::SSL too old') if $@; + +my $t = Test::Nginx->new()->has(qw/http http_ssl rewrite/) + ->has_daemon('openssl'); + +$t->{_configure_args} =~ /OpenSSL (\d+)/; +plan(skip_all => 'OpenSSL too old') unless defined $1 and $1 >= 3; + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + ssl_certificate_key localhost.key; + ssl_certificate localhost.crt; + + ssl_ecdh_curve prime256v1; + + server { + listen 127.0.0.1:8443 ssl; + server_name localhost; + + return 200 "$ssl_curve $ssl_curves"; + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('localhost') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->try_run('no $ssl_curve')->plan(1); + +############################################################################### + +like(get('/curve'), qr/^prime256v1 /m, 'ssl curve'); + +############################################################################### + +sub get { + my ($uri, $port, $ctx) = @_; + my $s = get_ssl_socket($port) or return; + my $r = http_get($uri, socket => $s); + $s->close(); + return $r; +} + +sub get_ssl_socket { + my ($port, $ctx) = @_; + my $s; + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(8); + $s = IO::Socket::SSL->new( + Proto => 'tcp', + PeerAddr => '127.0.0.1', + PeerPort => port(8443), + SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), + SSL_error_trap => sub { die $_[1] }, + ); + alarm(0); + }; + alarm(0); + + if ($@) { + log_in("died: $@"); + return undef; + } + + return $s; +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/ssl_engine_keys.t b/tests/nginx-tests/nginx-tests/ssl_engine_keys.t index e47c25dd90..853a45212e 100644 --- a/tests/nginx-tests/nginx-tests/ssl_engine_keys.t +++ b/tests/nginx-tests/nginx-tests/ssl_engine_keys.t @@ -28,7 +28,7 @@ plan(skip_all => 'may not work, leaves coredump') unless $ENV{TEST_NGINX_UNSAFE}; my $t = Test::Nginx->new()->has(qw/http proxy http_ssl/)->has_daemon('openssl') - ->has_daemon('softhsm')->has_daemon('pkcs11-tool')->plan(1); + ->has_daemon('softhsm2-util')->has_daemon('pkcs11-tool')->plan(2); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -48,14 +48,33 @@ http { server_name localhost; ssl_certificate localhost.crt; - ssl_certificate_key engine:pkcs11:slot_0-id_00; + ssl_certificate_key engine:pkcs11:id_00; location / { # index index.html by default } + location /proxy { proxy_pass https://127.0.0.1:8081/; } + + location /var { + proxy_pass https://127.0.0.1:8082/; + proxy_ssl_name localhost; + proxy_ssl_server_name on; + } + } + + server { + listen 127.0.0.1:8082 ssl; + server_name localhost; + + ssl_certificate $ssl_server_name.crt; + ssl_certificate_key engine:pkcs11:id_00; + + location / { + # index index.html by default + } } } @@ -82,7 +101,7 @@ pkcs11 = pkcs11_section [pkcs11_section] engine_id = pkcs11 dynamic_path = /usr/local/lib/engines/pkcs11.so -MODULE_PATH = /usr/local/lib/softhsm/libsofthsm.so +MODULE_PATH = /usr/local/lib/softhsm/libsofthsm2.so init = 1 PIN = 1234 @@ -95,25 +114,28 @@ EOF my $d = $t->testdir(); -$t->write_file('softhsm.conf', <write_file('softhsm2.conf', <>$d/openssl.out 2>&1"); - system('pkcs11-tool --module=/usr/local/lib/softhsm/libsofthsm.so ' - . '-p 1234 -l -k -d 0 -a nx_key_0 --key-type rsa:1024 ' + system('pkcs11-tool --module=/usr/local/lib/softhsm/libsofthsm2.so ' + . '-p 1234 -l -k -d 0 -a nx_key_0 --key-type rsa:2048 ' . ">>$d/openssl.out 2>&1"); - system('openssl req -x509 -new -engine pkcs11 ' - . "-config $d/openssl.conf -subj /CN=$name/ " - . "-out $d/$name.crt -keyform engine -text -key id_00 " + system('openssl req -x509 -new ' + . "-subj /CN=$name/ -out $d/$name.crt -text " + . "-engine pkcs11 -keyform engine -key id_00 " . ">>$d/openssl.out 2>&1") == 0 or die "Can't create certificate for $name: $!\n"; } @@ -125,5 +147,6 @@ $t->write_file('index.html', ''); ############################################################################### like(http_get('/proxy'), qr/200 OK/, 'ssl engine keys'); +like(http_get('/var'), qr/200 OK/, 'ssl_certificate with variable'); ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/ssl_reject_handshake.t b/tests/nginx-tests/nginx-tests/ssl_reject_handshake.t new file mode 100644 index 0000000000..3386b35ca1 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/ssl_reject_handshake.t @@ -0,0 +1,183 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for http ssl module, ssl_reject_handshake. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { require IO::Socket::SSL; }; +plan(skip_all => 'IO::Socket::SSL not installed') if $@; +eval { IO::Socket::SSL->can_client_sni() or die; }; +plan(skip_all => 'IO::Socket::SSL with OpenSSL SNI support required') if $@; + +my $t = Test::Nginx->new()->has(qw/http http_ssl sni/)->has_daemon('openssl'); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + add_header X-Name $ssl_server_name; + + server { + listen 127.0.0.1:8080 ssl; + server_name localhost; + + ssl_reject_handshake on; + } + + server { + listen 127.0.0.1:8081; + server_name ssl; + + ssl on; + ssl_reject_handshake on; + } + + server { + listen 127.0.0.1:8080 ssl; + listen 127.0.0.1:8081 ssl; + server_name virtual; + + ssl_certificate localhost.crt; + ssl_certificate_key localhost.key; + } + + server { + listen 127.0.0.1:8082 ssl; + server_name localhost; + + ssl_certificate localhost.crt; + ssl_certificate_key localhost.key; + } + + server { + listen 127.0.0.1:8082 ssl; + server_name virtual1; + } + + server { + listen 127.0.0.1:8082 ssl; + server_name virtual2; + + ssl_reject_handshake on; + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('localhost') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->write_file('index.html', ''); + +# suppress deprecation warning + +open OLDERR, ">&", \*STDERR; close STDERR; +$t->run()->plan(9); +open STDERR, ">&", \*OLDERR; + +############################################################################### + +# default virtual server rejected + +like(get('default', 8080), qr/unrecognized name/, 'default rejected'); +like(get(undef, 8080), qr/unrecognized name/, 'absent sni rejected'); +like(get('virtual', 8080), qr/virtual/, 'virtual accepted'); + +# default virtual server rejected - ssl on + +like(get('default', 8081), qr/unrecognized name/, 'default rejected - ssl on'); +like(get('virtual', 8081), qr/virtual/, 'virtual accepted - ssl on'); + +# non-default server "virtual2" rejected + +like(get('default', 8082), qr/default/, 'default accepted'); +like(get(undef, 8082), qr/200 OK(?!.*X-Name)/is, 'absent sni accepted'); +like(get('virtual1', 8082), qr/virtual1/, 'virtual 1 accepted'); +like(get('virtual2', 8082), qr/unrecognized name/, 'virtual 2 rejected'); + +############################################################################### + +sub get { + my ($host, $port) = @_; + my $s = get_ssl_socket($host, $port) or return $@; + $host = 'localhost' if !defined $host; + my $r = http(< $s); +GET / HTTP/1.0 +Host: $host + +EOF + + $s->close(); + return $r; +} + +sub get_ssl_socket { + my ($host, $port) = @_; + my $s; + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(8); + $s = IO::Socket::SSL->new( + Proto => 'tcp', + PeerAddr => '127.0.0.1', + PeerPort => port($port), + SSL_hostname => $host, + SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), + SSL_error_trap => sub { die $_[1] }, + ); + alarm(0); + }; + alarm(0); + + if ($@) { + log_in("died: $@"); + return undef; + } + + return $s; +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/ssl_sni.t b/tests/nginx-tests/nginx-tests/ssl_sni.t index 659a0d40c7..d27d261d08 100644 --- a/tests/nginx-tests/nginx-tests/ssl_sni.t +++ b/tests/nginx-tests/nginx-tests/ssl_sni.t @@ -46,6 +46,10 @@ http { location / { return 200 $server_name; } + + location /protocol { + return 200 $ssl_protocol; + } } server { @@ -59,6 +63,18 @@ http { return 200 $server_name; } } + + server { + listen 127.0.0.1:8081 ssl; + server_name localhost; + + ssl_certificate_key localhost.key; + ssl_certificate localhost.crt; + + location / { + return 200 $ssl_session_reused:$ssl_server_name; + } + } } EOF @@ -80,7 +96,7 @@ eval { }; plan(skip_all => 'Net::SSLeay with OpenSSL SNI support required') if $@; -$t->plan(6); +$t->plan(8); $t->write_file('openssl.conf', < IO::Socket::SSL::SSL_VERIFY_NONE(), + SSL_session_cache_size => 100); + +like(get('/', 'localhost', 8081, $ctx), qr/^\.:localhost$/m, 'ssl server name'); + +SKIP: { +skip 'no TLS 1.3 sessions', 1 if get('/protocol', 'localhost') =~ /TLSv1.3/ + && ($Net::SSLeay::VERSION < 1.88 || $IO::Socket::SSL::VERSION < 2.061); + +like(get('/', 'localhost', 8081, $ctx), qr/^r:localhost$/m, + 'ssl server name - reused'); + +} + ############################################################################### sub get_ssl_socket { - my ($host) = @_; + my ($host, $port, $ctx) = @_; my $s; eval { @@ -136,8 +169,9 @@ sub get_ssl_socket { alarm(8); $s = IO::Socket::SSL->new( Proto => 'tcp', - PeerAddr => '127.0.0.1:' . port(8080), + PeerAddr => '127.0.0.1:' . port($port || 8080), SSL_hostname => $host, + SSL_reuse_ctx => $ctx, SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), SSL_error_trap => sub { die $_[1] } ); @@ -171,4 +205,12 @@ Host: $host EOF } +sub get { + my ($uri, $host, $port, $ctx) = @_; + my $s = get_ssl_socket($host, $port, $ctx) or return; + my $r = http_get($uri, socket => $s); + $s->close(); + return $r; +} + ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/ssl_sni_reneg.t b/tests/nginx-tests/nginx-tests/ssl_sni_reneg.t index 2f3166203e..86a4b19c86 100644 --- a/tests/nginx-tests/nginx-tests/ssl_sni_reneg.t +++ b/tests/nginx-tests/nginx-tests/ssl_sni_reneg.t @@ -12,7 +12,7 @@ use strict; use Test::More; -use Socket qw/ :DEFAULT CRLF /; +use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -104,8 +104,6 @@ $t->plan(8); ############################################################################### -my ($ossl) = $t->{_configure_args} =~ /OpenSSL ([\d\.]+)/; - my ($s, $ssl) = get_ssl_socket(8080); ok($s, 'connection'); @@ -121,18 +119,11 @@ ok(Net::SSLeay::set_tlsext_host_name($ssl, 'localhost'), 'SNI'); Net::SSLeay::write($ssl, 'Host: localhost' . CRLF . CRLF); -TODO: { -local $TODO = 'not yet' if $ossl ge '1.1.1' and $^O eq 'linux' - and !$t->has_version('1.15.2'); - ok(!Net::SSLeay::read($ssl), 'response'); } -} - # virtual servers -# in [1.15.4..1.15.5) SSL_OP_NO_RENEGOTIATION is cleared in servername callback ($s, $ssl) = get_ssl_socket(8081); ok($s, 'connection 2'); @@ -149,31 +140,21 @@ ok(Net::SSLeay::set_tlsext_host_name($ssl, 'localhost'), 'SNI'); Net::SSLeay::write($ssl, 'Host: localhost' . CRLF . CRLF); -TODO: { -local $TODO = 'not yet' if $ossl ge '1.1.1' and $^O eq 'linux' - and !$t->has_version('1.15.2'); - ok(!Net::SSLeay::read($ssl), 'virtual servers'); } -} - ############################################################################### sub get_ssl_socket { my ($port) = @_; my $s; - my $dest_ip = inet_aton('127.0.0.1'); - my $dest_serv_params = sockaddr_in(port($port), $dest_ip); - eval { local $SIG{ALRM} = sub { die "timeout\n" }; local $SIG{PIPE} = sub { die "sigpipe\n" }; alarm(8); - socket($s, &AF_INET, &SOCK_STREAM, 0) or die "socket: $!"; - connect($s, $dest_serv_params) or die "connect: $!"; + $s = IO::Socket::INET->new('127.0.0.1:' . port($port)); alarm(0); }; alarm(0); diff --git a/tests/nginx-tests/nginx-tests/ssl_sni_sessions.t b/tests/nginx-tests/nginx-tests/ssl_sni_sessions.t index e38d5786e8..35ef7538d0 100644 --- a/tests/nginx-tests/nginx-tests/ssl_sni_sessions.t +++ b/tests/nginx-tests/nginx-tests/ssl_sni_sessions.t @@ -46,7 +46,7 @@ http { ssl_session_cache shared:cache1:1m; location / { - return 200 $ssl_server_name:$ssl_session_reused; + return 200 $ssl_server_name:$ssl_session_reused:$ssl_protocol; } } @@ -104,8 +104,6 @@ eval { }; plan(skip_all => 'Net::SSLeay with OpenSSL SNI support required') if $@; -$t->plan(6); - $t->write_file('openssl.conf', <write_file('ticket2.key', '2' x 48); $t->run(); +plan(skip_all => 'no TLS 1.3 sessions') + if get('default', port(8080), get_ssl_context()) =~ /TLSv1.3/ + && ($Net::SSLeay::VERSION < 1.88 || $IO::Socket::SSL::VERSION < 2.061); + +$t->plan(6); + ############################################################################### # check that everything works fine with default server diff --git a/tests/nginx-tests/nginx-tests/ssl_stapling.t b/tests/nginx-tests/nginx-tests/ssl_stapling.t index 2e6d8ba83b..c6617a9a82 100644 --- a/tests/nginx-tests/nginx-tests/ssl_stapling.t +++ b/tests/nginx-tests/nginx-tests/ssl_stapling.t @@ -401,7 +401,14 @@ Content-Type: application/ocsp-response EOF - print $client $headers . $t->read_file("$resp.der"); + local $/; + open my $fh, '<', "$d/$resp.der" + or die "Can't open $resp.der: $!"; + binmode $fh; + my $content = <$fh>; + close $fh; + + print $client $headers . $content; } } diff --git a/tests/nginx-tests/nginx-tests/ssl_verify_client.t b/tests/nginx-tests/nginx-tests/ssl_verify_client.t index c25ad6483a..ce5fb80502 100644 --- a/tests/nginx-tests/nginx-tests/ssl_verify_client.t +++ b/tests/nginx-tests/nginx-tests/ssl_verify_client.t @@ -12,7 +12,7 @@ use strict; use Test::More; -use Socket qw/ :DEFAULT CRLF /; +use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -40,7 +40,7 @@ eval { plan(skip_all => 'Net::SSLeay with OpenSSL SNI support required') if $@; my $t = Test::Nginx->new()->has(qw/http http_ssl sni/) - ->has_daemon('openssl')->plan(11); + ->has_daemon('openssl')->plan(13); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -95,7 +95,19 @@ http { server { listen 127.0.0.1:8081 ssl; - server_name optional_no_ca; + server_name off; + + ssl_certificate_key 1.example.com.key; + ssl_certificate 1.example.com.crt; + + ssl_verify_client off; + ssl_client_certificate 2.example.com.crt; + ssl_trusted_certificate 3.example.com.crt; + } + + server { + listen 127.0.0.1:8081 ssl; + server_name optional.no.ca; ssl_certificate_key 1.example.com.key; ssl_certificate 1.example.com.crt; @@ -106,7 +118,7 @@ http { server { listen 127.0.0.1:8081; - server_name no_context; + server_name no.context; ssl_verify_client on; } @@ -140,14 +152,15 @@ $t->run(); ############################################################################### - like(http_get('/t'), qr/x:x/, 'plain connection'); like(get('on'), qr/400 Bad Request/, 'no cert'); -like(get('no_context'), qr/400 Bad Request/, 'no server cert'); +like(get('no.context'), qr/400 Bad Request/, 'no server cert'); like(get('optional'), qr/NONE:x/, 'no optional cert'); like(get('optional', '1.example.com'), qr/400 Bad/, 'bad optional cert'); -like(get('optional_no_ca', '1.example.com'), qr/FAILED.*BEGIN/, +like(get('optional.no.ca', '1.example.com'), qr/FAILED.*BEGIN/, 'bad optional_no_ca cert'); +like(get('off', '2.example.com'), qr/NONE/, 'off cert'); +like(get('off', '3.example.com'), qr/NONE/, 'off cert trusted'); like(get('localhost', '2.example.com'), qr/SUCCESS.*BEGIN/, 'good cert'); like(get('optional', '2.example.com'), qr/SUCCESS.*BEGI/, 'good cert optional'); @@ -172,12 +185,7 @@ sub get { $host = $sni if !defined $host; - my $dest_ip = inet_aton('127.0.0.1'); - my $dest_serv_params = sockaddr_in(port(8081), $dest_ip); - - socket(my $s, &AF_INET, &SOCK_STREAM, 0) or die "socket: $!"; - connect($s, $dest_serv_params) or die "connect: $!"; - + my $s = IO::Socket::INET->new('127.0.0.1:' . port(8081)); my $ctx = Net::SSLeay::CTX_new() or die("Failed to create SSL_CTX $!"); Net::SSLeay::set_cert_and_key($ctx, "$d/$cert.crt", "$d/$cert.key") or die if $cert; diff --git a/tests/nginx-tests/nginx-tests/ssl_verify_depth.t b/tests/nginx-tests/nginx-tests/ssl_verify_depth.t index 37a8c10a32..4e010cce54 100644 --- a/tests/nginx-tests/nginx-tests/ssl_verify_depth.t +++ b/tests/nginx-tests/nginx-tests/ssl_verify_depth.t @@ -27,10 +27,11 @@ plan(skip_all => 'IO::Socket::SSL not installed') if $@; eval { IO::Socket::SSL::SSL_VERIFY_NONE(); }; plan(skip_all => 'IO::Socket::SSL too old') if $@; -my $t = Test::Nginx->new()->has(qw/http http_ssl/) - ->has_daemon('openssl')->plan(2); +my $t = Test::Nginx->new()->has(qw/http http_ssl/)->has_daemon('openssl'); -$t->write_file_expand('nginx.conf', <<'EOF'); +plan(skip_all => 'LibreSSL') if $t->has_module('LibreSSL'); + +$t->plan(9)->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -42,19 +43,32 @@ events { http { %%TEST_GLOBALS_HTTP%% - ssl_certificate_key localhost.key; ssl_certificate localhost.crt; + ssl_certificate_key localhost.key; ssl_verify_client on; - ssl_client_certificate int-root.crt; + ssl_client_certificate root-int.crt; - add_header X-Verify $ssl_client_verify; + add_header X-Client $ssl_client_s_dn always; + add_header X-Verify $ssl_client_verify always; server { listen 127.0.0.1:8080 ssl; server_name localhost; ssl_verify_depth 0; } + + server { + listen 127.0.0.1:8081 ssl; + server_name localhost; + ssl_verify_depth 1; + } + + server { + listen 127.0.0.1:8082 ssl; + server_name localhost; + ssl_verify_depth 2; + } } EOF @@ -80,9 +94,13 @@ default_md = sha256 policy = myca_policy serial = $d/certserial default_days = 1 +x509_extensions = myca_extensions [ myca_policy ] commonName = supplied + +[ myca_extensions ] +basicConstraints = critical,CA:TRUE EOF foreach my $name ('root', 'localhost') { @@ -116,23 +134,50 @@ system("openssl ca -batch -config $d/ca.conf " . ">>$d/openssl.out 2>&1") == 0 or die "Can't sign certificate for end: $!\n"; -$t->write_file('int-root.crt', - $t->read_file('int.crt') . $t->read_file('root.crt')); +$t->write_file('root-int.crt', $t->read_file('root.crt') + . $t->read_file('int.crt')); $t->write_file('t', ''); $t->run(); ############################################################################### -like(get(8080, 'root'), qr/SUCCESS/, 'verify depth'); -like(get(8080, 'end'), qr/400 Bad Request/, 'verify depth limited'); +# with verify depth 0, only self-signed certificates should +# be allowed + +# OpenSSL 1.1.0+ instead limits the number of intermediate certs allowed; +# as a result, it is not possible to limit certificate checking +# to self-signed certificates only when using OpenSSL 1.1.0+ + +like(get(8080, 'root'), qr/SUCCESS/, 'verify depth 0 - root'); +like(get(8080, 'int'), qr/FAI|SUC/, 'verify depth 0 - no int'); +like(get(8080, 'end'), qr/FAILED/, 'verify depth 0 - no end'); + +# with verify depth 1 (the default), one signature is +# expected to be checked, so certificates directly signed +# by the root cert are allowed, but nothing more + +# OpenSSL 1.1.0+ instead limits the number of intermediate certs allowed; +# so with depth 1 it is possible to validate not only directly signed +# certificates, but also chains with one intermediate certificate + +like(get(8081, 'root'), qr/SUCCESS/, 'verify depth 1 - root'); +like(get(8081, 'int'), qr/SUCCESS/, 'verify depth 1 - int'); +like(get(8081, 'end'), qr/FAI|SUC/, 'verify depth 1 - no end'); + +# with verify depth 2 it is also possible to validate up to two signatures, +# so chains with one intermediate certificate are allowed + +like(get(8082, 'root'), qr/SUCCESS/, 'verify depth 2 - root'); +like(get(8082, 'int'), qr/SUCCESS/, 'verify depth 2 - int'); +like(get(8082, 'end'), qr/SUCCESS/, 'verify depth 2 - end'); ############################################################################### sub get { my ($port, $cert) = @_; my $s = get_ssl_socket($port, $cert) or return; - http_get('/t', socket => $s); + http_get("/t?$cert", socket => $s); } sub get_ssl_socket { diff --git a/tests/nginx-tests/nginx-tests/stream_access.t b/tests/nginx-tests/nginx-tests/stream_access.t index 2ffba82bd4..1a5a420f7a 100644 --- a/tests/nginx-tests/nginx-tests/stream_access.t +++ b/tests/nginx-tests/nginx-tests/stream_access.t @@ -35,6 +35,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + server { listen 127.0.0.1:8082; proxy_pass [::1]:%%PORT_8080%%; diff --git a/tests/nginx-tests/nginx-tests/stream_access_log.t b/tests/nginx-tests/nginx-tests/stream_access_log.t index 0df1636ae0..6fd4c12c01 100644 --- a/tests/nginx-tests/nginx-tests/stream_access_log.t +++ b/tests/nginx-tests/nginx-tests/stream_access_log.t @@ -12,6 +12,8 @@ use strict; use Test::More; +use Sys::Hostname; + BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; @@ -35,6 +37,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + log_format test $server_addr; log_format vars $connection:$nginx_version:$hostname:$pid; log_format addr $binary_remote_addr:$remote_addr:$remote_port: @@ -43,7 +47,7 @@ stream { log_format byte $bytes_received:$bytes_sent: $upstream_bytes_sent:$upstream_bytes_received; log_format time $upstream_connect_time:$upstream_first_byte_time: - $upstream_session_time; + $upstream_session_time:$session_time; access_log %%TESTDIR%%/off.log test; @@ -61,6 +65,7 @@ stream { server { listen 127.0.0.1:8082; proxy_pass 127.0.0.1:8080; + proxy_download_rate 2; access_log %%TESTDIR%%/time.log time; } @@ -157,14 +162,14 @@ is($t->read_file('filtered.log'), "127.0.0.1\n", 'log filtering'); ok($t->read_file('complex.log'), 'if with complex value'); ok($t->read_file('varlog_3.log'), 'variable in file'); -chomp(my $hostname = lc `hostname`); +my $hostname = lc hostname(); like($t->read_file('vars.log'), qr/^\d+:[\d.]+:$hostname:\d+$/, 'log vars'); is($t->read_file('addr.log'), "$escaped:$lhost:$lport:127.0.0.1:$dport:127.0.0.1:$uport\n", 'log addr'); like($t->read_file('date.log'), qr#^\d+.\d+![-+\w/: ]+![-+\dT:]+$#, 'log date'); is($t->read_file('byte.log'), "8:3:8:3\n", 'log bytes'); -like($t->read_file('time.log'), qr/0\.\d{3}:0\.\d{3}:0\.\d{3}/, 'log time'); +like($t->read_file('time.log'), qr/0\.\d+:0\.\d+:1\.\d+:1\.\d+/, 'log time'); ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_access_log_escape.t b/tests/nginx-tests/nginx-tests/stream_access_log_escape.t index 40d63e491d..3a5adc7824 100644 --- a/tests/nginx-tests/nginx-tests/stream_access_log_escape.t +++ b/tests/nginx-tests/nginx-tests/stream_access_log_escape.t @@ -33,6 +33,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + map $pid $a { default '" \ "'; } diff --git a/tests/nginx-tests/nginx-tests/stream_access_log_none.t b/tests/nginx-tests/nginx-tests/stream_access_log_none.t index 5cd4ca0b85..75db734ae3 100644 --- a/tests/nginx-tests/nginx-tests/stream_access_log_none.t +++ b/tests/nginx-tests/nginx-tests/stream_access_log_none.t @@ -33,6 +33,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + map $pid $a { default '" \ "'; } diff --git a/tests/nginx-tests/nginx-tests/stream_error_log.t b/tests/nginx-tests/nginx-tests/stream_error_log.t index d5df787bf3..c7578814ee 100644 --- a/tests/nginx-tests/nginx-tests/stream_error_log.t +++ b/tests/nginx-tests/nginx-tests/stream_error_log.t @@ -13,6 +13,7 @@ use strict; use Test::More; use IO::Select; +use Sys::Hostname; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -43,6 +44,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + upstream u { server 127.0.0.1:%%PORT_8983_UDP%% down; } @@ -241,8 +244,7 @@ SKIP: { ok($sec < 60, "$desc valid seconds"); ok(defined($host), "$desc has host"); - chomp(my $hostname = lc `hostname`); - is($host , $hostname, "$desc valid host"); + is($host, lc(hostname()), "$desc valid host"); ok(defined($tag), "$desc has tag"); like($tag, qr'\w+', "$desc valid tag"); diff --git a/tests/nginx-tests/nginx-tests/stream_geo.t b/tests/nginx-tests/nginx-tests/stream_geo.t index d263ec9265..02531ab530 100644 --- a/tests/nginx-tests/nginx-tests/stream_geo.t +++ b/tests/nginx-tests/nginx-tests/stream_geo.t @@ -37,6 +37,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + geo $geo { 127.0.0.0/8 loopback; 192.0.2.0/24 test; diff --git a/tests/nginx-tests/nginx-tests/stream_geo_binary.t b/tests/nginx-tests/nginx-tests/stream_geo_binary.t index 4dfc5329e4..cb2f59487f 100644 --- a/tests/nginx-tests/nginx-tests/stream_geo_binary.t +++ b/tests/nginx-tests/nginx-tests/stream_geo_binary.t @@ -37,6 +37,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + geo $geo_base_create { ranges; include base.conf; diff --git a/tests/nginx-tests/nginx-tests/stream_geo_ipv6.t b/tests/nginx-tests/nginx-tests/stream_geo_ipv6.t index 2b150a42dd..bd698191b5 100644 --- a/tests/nginx-tests/nginx-tests/stream_geo_ipv6.t +++ b/tests/nginx-tests/nginx-tests/stream_geo_ipv6.t @@ -34,6 +34,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + geo $geo { ::1/128 loopback; 2001:0db8::/32 test; diff --git a/tests/nginx-tests/nginx-tests/stream_geo_unix.t b/tests/nginx-tests/nginx-tests/stream_geo_unix.t index 9aa971bc8c..2f3e7a7e74 100644 --- a/tests/nginx-tests/nginx-tests/stream_geo_unix.t +++ b/tests/nginx-tests/nginx-tests/stream_geo_unix.t @@ -36,6 +36,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + geo $geo { default default; 255.255.255.255 none; @@ -77,15 +79,8 @@ $t->run(); ############################################################################### my %data = stream('127.0.0.1:' . port(8080))->read() =~ /(\w+):(\w+)/g; - -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.15.8'); - is($data{geo}, 'none', 'geo unix'); is($data{geor}, 'none', 'geo unix ranges'); - -} - is($data{geora}, 'none', 'geo unix remote addr'); is($data{georra}, 'none', 'geo unix ranges remote addr'); diff --git a/tests/nginx-tests/nginx-tests/stream_geoip.t b/tests/nginx-tests/nginx-tests/stream_geoip.t index bef3840d64..88f9f1101c 100644 --- a/tests/nginx-tests/nginx-tests/stream_geoip.t +++ b/tests/nginx-tests/nginx-tests/stream_geoip.t @@ -38,6 +38,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + set_real_ip_from 127.0.0.1/32; geoip_country %%TESTDIR%%/country.dat; diff --git a/tests/nginx-tests/nginx-tests/stream_js.t b/tests/nginx-tests/nginx-tests/stream_js.t index 332babd2e0..6e174bcab7 100644 --- a/tests/nginx-tests/nginx-tests/stream_js.t +++ b/tests/nginx-tests/nginx-tests/stream_js.t @@ -37,14 +37,14 @@ events { http { %%TEST_GLOBALS_HTTP%% - js_include test.js; + js_import test.js; server { listen 127.0.0.1:8079; server_name localhost; location /njs { - js_content test_njs; + js_content test.njs; } location /p/ { @@ -59,14 +59,19 @@ http { } stream { - js_set $js_addr js_addr; - js_set $js_var js_var; - js_set $js_log js_log; - js_set $js_unk js_unk; - js_set $js_req_line js_req_line; - js_set $js_sess_unk js_sess_unk; + %%TEST_GLOBALS_STREAM%% - js_include test.js; + js_set $js_addr test.addr; + js_set $js_var test.variable; + js_set $js_log test.log; + js_set $js_unk test.unk; + js_set $js_req_line test.req_line; + js_set $js_sess_unk test.sess_unk; + js_set $js_async test.asyncf; + + js_import test.js; + + log_format status $server_port:$status; server { listen 127.0.0.1:8080; @@ -100,83 +105,91 @@ stream { server { listen 127.0.0.1:8086; - js_access js_access_step; - js_preread js_preread_step; - js_filter js_filter_step; + js_access test.access_step; + js_preread test.preread_step; + js_filter test.filter_step; proxy_pass 127.0.0.1:8090; } server { listen 127.0.0.1:8087; - js_access js_access_undecided; + js_access test.access_undecided; return OK; + access_log %%TESTDIR%%/status.log status; } server { listen 127.0.0.1:8088; - js_access js_access_allow; + js_access test.access_allow; return OK; + access_log %%TESTDIR%%/status.log status; } server { listen 127.0.0.1:8089; - js_access js_access_deny; + js_access test.access_deny; return OK; + access_log %%TESTDIR%%/status.log status; } server { listen 127.0.0.1:8091; - js_preread js_preread_async; + js_preread test.preread_async; proxy_pass 127.0.0.1:8090; } server { listen 127.0.0.1:8092; - js_preread js_preread_data; + js_preread test.preread_data; proxy_pass 127.0.0.1:8090; } server { listen 127.0.0.1:8093; - js_preread js_preread_req_line; + js_preread test.preread_req_line; return $js_req_line; } server { listen 127.0.0.1:8094; - js_filter js_filter_empty; + js_filter test.filter_empty; proxy_pass 127.0.0.1:8090; } server { listen 127.0.0.1:8095; - js_filter js_filter_header_inject; + js_filter test.filter_header_inject; proxy_pass 127.0.0.1:8079; } server { listen 127.0.0.1:8096; - js_filter js_filter_search; + js_filter test.filter_search; proxy_pass 127.0.0.1:8090; } server { listen 127.0.0.1:8097; - js_access js_access_except; + js_access test.access_except; proxy_pass 127.0.0.1:8090; } server { listen 127.0.0.1:8098; - js_preread js_preread_except; + js_preread test.preread_except; proxy_pass 127.0.0.1:8090; } server { listen 127.0.0.1:8099; - js_filter js_filter_except; + js_filter test.filter_except; proxy_pass 127.0.0.1:8090; } + + server { + listen 127.0.0.1:8100; + return $js_async; + } } EOF @@ -186,25 +199,25 @@ $t->write_file('test.js', <write_file('test.js', <= 3) { @@ -223,7 +236,7 @@ $t->write_file('test.js', <write_file('test.js', <write_file('test.js', <write_file('test.js', <write_file('test.js', <write_file('test.js', <write_file('test.js', < {resolve(x)}).then(v => v).then(v => v); + } + + async function asyncf(s) { + const a1 = await pr(10); + const a2 = await pr(20); + + s.setReturnValue(`retval: \${a1 + a2}`); + } + + export default {njs:test_njs, addr, variable, sess_unk, log, access_step, + preread_step, filter_step, access_undecided, access_allow, + access_deny, preread_async, preread_data, preread_req_line, + req_line, filter_empty, filter_header_inject, filter_search, + access_except, preread_except, filter_except, asyncf}; + EOF $t->run_daemon(\&stream_daemon, port(8090)); -$t->try_run('no stream njs available')->plan(19); +$t->try_run('no stream njs available')->plan(23); $t->waitforsocket('127.0.0.1:' . port(8090)); ############################################################################### @@ -367,38 +397,45 @@ is(stream('127.0.0.1:' . port(8082))->read(), 'variable=127.0.0.1', is(stream('127.0.0.1:' . port(8083))->read(), '', 'stream js unknown function'); is(stream('127.0.0.1:' . port(8084))->read(), 'sess_unk=undefined', 's.unk'); -TODO: { -local $TODO = 'not yet' - unless get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.2.4'; - is(stream('127.0.0.1:' . port(8086))->io('0'), '0122345', 'async handlers order'); -is(stream('127.0.0.1:' . port(8087))->io('#'), 'OK', 'js_access_undecided'); -is(stream('127.0.0.1:' . port(8088))->io('#'), 'OK', 'js_access_allow'); -is(stream('127.0.0.1:' . port(8089))->io('#'), '', 'js_access_deny'); - -is(stream('127.0.0.1:' . port(8091))->io('#'), '#', 'js_preread_async'); -is(stream('127.0.0.1:' . port(8092))->io('#z'), '#z', 'js_preread_async_data'); -is(stream('127.0.0.1:' . port(8093))->io("xy\na"), 'xy', 'js_preread_req_line'); +is(stream('127.0.0.1:' . port(8087))->io('#'), 'OK', 'access_undecided'); +is(stream('127.0.0.1:' . port(8088))->io('#'), 'OK', 'access_allow'); +is(stream('127.0.0.1:' . port(8089))->io('#'), '', 'access_deny'); -is(stream('127.0.0.1:' . port(8094))->io('x'), 'x', 'js_filter_empty'); -like(get('/p/return'), qr/foo/, 'js_filter_injected_header'); -is(stream('127.0.0.1:' . port(8096))->io('x'), 'z', 'js_filter_search'); +is(stream('127.0.0.1:' . port(8091))->io('#'), '#', 'preread_async'); +is(stream('127.0.0.1:' . port(8092))->io('#z'), '#z', 'preread_async_data'); +is(stream('127.0.0.1:' . port(8093))->io("xy\na"), 'xy', 'preread_req_line'); -} +is(stream('127.0.0.1:' . port(8094))->io('x'), 'x', 'filter_empty'); +like(get('/p/return'), qr/foo/, 'filter_injected_header'); +is(stream('127.0.0.1:' . port(8096))->io('x'), 'z', 'filter_search'); stream('127.0.0.1:' . port(8097))->io('x'); stream('127.0.0.1:' . port(8098))->io('x'); stream('127.0.0.1:' . port(8099))->io('x'); +TODO: { +local $TODO = 'not yet' + unless get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.7.0'; + +is(stream('127.0.0.1:' . port(8100))->read(), 'retval: 30', 'asyncf'); + +} + $t->stop(); ok(index($t->read_file('error.log'), 'SEE-THIS') > 0, 'stream js log'); ok(index($t->read_file('error.log'), 'at fs.readFileSync') > 0, 'stream js_preread backtrace'); -ok(index($t->read_file('error.log'), 'at js_filter_except') > 0, +ok(index($t->read_file('error.log'), 'at filter_except') > 0, 'stream js_filter backtrace'); +my @p = (port(8087), port(8088), port(8089)); +like($t->read_file('status.log'), qr/$p[0]:200/, 'status undecided'); +like($t->read_file('status.log'), qr/$p[1]:200/, 'status allow'); +like($t->read_file('status.log'), qr/$p[2]:403/, 'status deny'); + ############################################################################### sub stream_daemon { diff --git a/tests/nginx-tests/nginx-tests/stream_js_buffer.t b/tests/nginx-tests/nginx-tests/stream_js_buffer.t new file mode 100644 index 0000000000..8f1de5fd02 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_js_buffer.t @@ -0,0 +1,180 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for stream njs module, buffer properties. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy rewrite stream stream_return/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /p/ { + proxy_pass http://127.0.0.1:8085/; + } + + location /return { + return 200 'RETURN:$http_foo'; + } + } +} + +stream { + js_import test.js; + + js_set $type test.type; + js_set $binary_var test.binary_var; + + server { + listen 127.0.0.1:8081; + return $type; + } + + server { + listen 127.0.0.1:8082; + return $binary_var; + } + + server { + listen 127.0.0.1:8083; + js_preread test.cb_mismatch; + proxy_pass 127.0.0.1:8090; + } + + server { + listen 127.0.0.1:8084; + js_preread test.cb_mismatch2; + proxy_pass 127.0.0.1:8090; + } + + server { + listen 127.0.0.1:8085; + js_filter test.header_inject; + proxy_pass 127.0.0.1:8080; + } +} + +EOF + +$t->write_file('test.js', < {}); + s.on('downstream', () => {}); + } catch (e) { + throw new Error(`cb_mismatch:\${e.message}`) + } + } + + function cb_mismatch2(s) { + try { + s.on('upstream', () => {}); + s.on('download', () => {}); + } catch (e) { + throw new Error(`cb_mismatch2:\${e.message}`) + } + } + + function header_inject(s) { + var req = Buffer.from([]); + + s.on('upstream', function(data, flags) { + req = Buffer.concat([req, data]); + + var n = req.indexOf('\\n'); + if (n != -1) { + var rest = req.slice(n + 1); + req = req.slice(0, n + 1); + + s.send(req, flags); + s.send('Foo: foo\\r\\n', flags); + s.send(rest, flags); + + s.off('upstream'); + } + }); + } + + export default {njs: test_njs, type, binary_var, cb_mismatch, cb_mismatch2, + header_inject}; + +EOF + +$t->try_run('no njs ngx')->plan(5); + +############################################################################### + +TODO: { +local $TODO = 'not yet' + unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.5.0'; + +is(stream('127.0.0.1:' . port(8081))->read(), 'buffer', 'var type'); +is(stream('127.0.0.1:' . port(8082))->read(), 'true', 'binary var'); + +stream('127.0.0.1:' . port(8083))->io('x'); +stream('127.0.0.1:' . port(8084))->io('x'); + +like(http_get('/p/return'), qr/RETURN:foo/, 'injected header'); + +$t->stop(); + +ok(index($t->read_file('error.log'), 'cb_mismatch:mixing string and buffer') + > 0, 'cb mismatch'); +ok(index($t->read_file('error.log'), 'cb_mismatch2:mixing string and buffer') + > 0, 'cb mismatch'); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_js_exit.t b/tests/nginx-tests/nginx-tests/stream_js_exit.t new file mode 100644 index 0000000000..257562ce3b --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_js_exit.t @@ -0,0 +1,155 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for stream njs module, exit hook. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http stream/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + } +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_import test.js; + + server { + listen 127.0.0.1:8081; + js_access test.access; + js_filter test.filter; + proxy_pass 127.0.0.1:8090; + } + + server { + listen 127.0.0.1:8082; + js_access test.access; + proxy_pass 127.0.0.1:1; + } +} + +EOF + +$t->write_file('test.js', < { + var v = s.variables; + var c = `\${v.bytes_received}/\${v.bytes_sent}`; + var u = `\${v.upstream_bytes_received}/\${v.upstream_bytes_sent}`; + s.error(`s:\${s.status} C: \${c} U: \${u}`); + }); + + s.allow(); + } + + function filter(s) { + s.on('upload', (data, flags) => { + s.send(`@\${data}`, flags); + }); + + s.on('download', (data, flags) => { + s.send(data.slice(2), flags); + }); + } + + export default {njs: test_njs, access, filter}; +EOF + +$t->try_run('no stream njs available')->plan(2); + +$t->run_daemon(\&stream_daemon, port(8090)); +$t->waitforsocket('127.0.0.1:' . port(8090)); + +############################################################################### + +local $TODO = 'not yet' + unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.5.2'; + +stream('127.0.0.1:' . port(8081))->io('###'); +stream('127.0.0.1:' . port(8082))->io('###'); + +$t->stop(); + +ok(index($t->read_file('error.log'), 's:200 C: 3/6 U: 8/4') > 0, 'normal'); +ok(index($t->read_file('error.log'), 's:502 C: 0/0 U: 0/0') > 0, 'failed conn'); + +############################################################################### + +sub stream_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . port(8090), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + log2c("(new connection $client)"); + + $client->sysread(my $buffer, 65536) or next; + + log2i("$client $buffer"); + + $buffer = $buffer . $buffer; + + log2o("$client $buffer"); + + $client->syswrite($buffer); + + close $client; + } +} + +sub log2i { Test::Nginx::log_core('|| <<', @_); } +sub log2o { Test::Nginx::log_core('|| >>', @_); } +sub log2c { Test::Nginx::log_core('||', @_); } + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_js_fetch.t b/tests/nginx-tests/nginx-tests/stream_js_fetch.t new file mode 100644 index 0000000000..0fc930a7a9 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_js_fetch.t @@ -0,0 +1,232 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for stream njs module, fetch method. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http stream/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + } + + server { + listen 127.0.0.1:8080; + server_name aaa; + + location /validate { + js_content test.validate; + } + } +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_import test.js; + + server { + listen 127.0.0.1:8081; + js_preread test.preread_verify; + proxy_pass 127.0.0.1:8090; + } + + server { + listen 127.0.0.1:8082; + js_filter test.filter_verify; + proxy_pass 127.0.0.1:8091; + } +} + +EOF + +my $p = port(8080); + +$t->write_file('test.js', <= 4 && collect.readUInt16BE(0) == 0xabcd) { + s.off('upstream'); + + let reply = await ngx.fetch('http://127.0.0.1:$p/validate', + {body: collect.slice(2,4), + headers: {Host:'aaa'}}); + + (reply.status == 200) ? s.done(): s.deny(); + + } else if (collect.length) { + s.deny(); + } + }); + } + + function filter_verify(s) { + var collect = Buffer.from([]); + + s.on('upstream', async function (data, flags) { + collect = Buffer.concat([collect, data]); + + if (collect.length >= 4 && collect.readUInt16BE(0) == 0xabcd) { + s.off('upstream'); + + let reply = await ngx.fetch('http://127.0.0.1:$p/validate', + {body: collect.slice(2,4), + headers: {Host:'aaa'}}); + + if (reply.status == 200) { + s.send(collect.slice(4), flags); + + } else { + s.send("__CLOSE__", flags); + } + } + }); + } + + export default {njs: test_njs, validate, preread_verify, filter_verify}; +EOF + +$t->try_run('no stream njs available')->plan(7); + +$t->run_daemon(\&stream_daemon, port(8090), port(8091)); +$t->waitforsocket('127.0.0.1:' . port(8090)); +$t->waitforsocket('127.0.0.1:' . port(8091)); + +############################################################################### + +local $TODO = 'not yet' + unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.5.1'; + +is(stream('127.0.0.1:' . port(8081))->io('###'), '', 'preread not enough'); +is(stream('127.0.0.1:' . port(8081))->io("\xAB\xCDQZ##"), "\xAB\xCDQZ##", + 'preread validated'); +is(stream('127.0.0.1:' . port(8081))->io("\xAC\xCDQZ##"), '', + 'preread invalid magic'); +is(stream('127.0.0.1:' . port(8081))->io("\xAB\xCDQQ##"), '', + 'preread validation failed'); + +TODO: { +todo_skip 'leaves coredump', 3 unless $ENV{TEST_NGINX_UNSAFE} + or http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.7.7'; + +my $s = stream('127.0.0.1:' . port(8082)); +is($s->io("\xAB\xCDQZ##", read => 1), '##', 'filter validated'); +is($s->io("@@", read => 1), '@@', 'filter off'); + +is(stream('127.0.0.1:' . port(8082))->io("\xAB\xCDQQ##"), '', + 'filter validation failed'); + +} + +############################################################################### + +sub stream_daemon { + my (@ports) = @_; + my (@socks, @clients); + + for my $port (@ports) { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => "127.0.0.1:$port", + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + push @socks, $server; + } + + my $sel = IO::Select->new(@socks); + + local $SIG{PIPE} = 'IGNORE'; + + while (my @ready = $sel->can_read) { + foreach my $fh (@ready) { + if (grep $_ == $fh, @socks) { + my $new = $fh->accept; + $new->autoflush(1); + $sel->add($new); + + } elsif (stream_handle_client($fh) + || $fh->sockport() == port(8090)) + { + $sel->remove($fh); + $fh->close; + } + } + } +} + +sub stream_handle_client { + my ($client) = @_; + + log2c("(new connection $client)"); + + $client->sysread(my $buffer, 65536) or return 1; + + log2i("$client $buffer"); + + if ($buffer eq "__CLOSE__") { + return 1; + } + + log2o("$client $buffer"); + + $client->syswrite($buffer); + + return 0; +} + +sub log2i { Test::Nginx::log_core('|| <<', @_); } +sub log2o { Test::Nginx::log_core('|| >>', @_); } +sub log2c { Test::Nginx::log_core('||', @_); } + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_js_fetch_https.t b/tests/nginx-tests/nginx-tests/stream_js_fetch_https.t new file mode 100644 index 0000000000..a5ab396f59 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_js_fetch_https.t @@ -0,0 +1,301 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for stream njs module, fetch method, https support. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_ssl rewrite stream stream_return/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + } + + server { + listen 127.0.0.1:8081 ssl default; + server_name default.example.com; + + ssl_certificate default.example.com.chained.crt; + ssl_certificate_key default.example.com.key; + + location /loc { + return 200 "You are at default.example.com."; + } + } + + server { + listen 127.0.0.1:8081 ssl; + server_name 1.example.com; + + ssl_certificate 1.example.com.chained.crt; + ssl_certificate_key 1.example.com.key; + + location /loc { + return 200 "You are at 1.example.com."; + } + } +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_import test.js; + js_preread test.preread; + js_var $message; + + resolver 127.0.0.1:%%PORT_8981_UDP%%; + resolver_timeout 1s; + + server { + listen 127.0.0.1:8082; + return "default CA $message"; + } + + server { + listen 127.0.0.1:8083; + return "my CA $message"; + + js_fetch_ciphers HIGH:!aNull:!MD5; + js_fetch_protocols TLSv1.1 TLSv1.2; + js_fetch_trusted_certificate myca.crt; + } + + server { + listen 127.0.0.1:8084; + return "my CA with verify_depth=0 $message"; + + js_fetch_verify_depth 0; + js_fetch_trusted_certificate myca.crt; + } +} + +EOF + +my $p1 = port(8081); +my $p2 = port(8082); +my $p3 = port(8083); +my $p4 = port(8084); + +$t->write_file('test.js', < { + s.variables.message = 'https OK - ' + reply.status; + s.done(); + }) + .catch(e => { + s.variables.message = 'https NOK - ' + e.message; + s.done(); + }) + + } else if (data.length) { + s.deny(); + } + }); + } + + export default {njs: test_njs, preread}; +EOF + +my $d = $t->testdir(); + +$t->write_file('openssl.conf', <write_file('myca.conf', <>$d/openssl.out 2>&1") == 0 + or die "Can't create self-signed certificate for CA: $!\n"; + +foreach my $name ('intermediate', 'default.example.com', '1.example.com') { + system("openssl req -new " + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.csr -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate signing req for $name: $!\n"; +} + +$t->write_file('certserial', '1000'); +$t->write_file('certindex', ''); + +system("openssl ca -batch -config $d/myca.conf " + . "-keyfile $d/myca.key -cert $d/myca.crt " + . "-subj /CN=intermediate/ -in $d/intermediate.csr " + . "-out $d/intermediate.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for intermediate: $!\n"; + +foreach my $name ('default.example.com', '1.example.com') { + system("openssl ca -batch -config $d/myca.conf " + . "-keyfile $d/intermediate.key -cert $d/intermediate.crt " + . "-subj /CN=$name/ -in $d/$name.csr -out $d/$name.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for $name $!\n"; + $t->write_file("$name.chained.crt", $t->read_file("$name.crt") + . $t->read_file('intermediate.crt')); +} + +$t->try_run('no njs.fetch')->plan(4); + +$t->run_daemon(\&dns_daemon, port(8981), $t); +$t->waitforfile($t->testdir . '/' . port(8981)); + +############################################################################### + +local $TODO = 'not yet' + unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.7.0'; + +like(stream("127.0.0.1:$p2")->io('GOdefault.example.com'), + qr/connect failed/s, 'stream non trusted CA'); +like(stream("127.0.0.1:$p3")->io('GOdefault.example.com'), + qr/https OK/s, 'stream trusted CA'); +like(stream("127.0.0.1:$p3")->io('GOlocalhost'), + qr/connect failed/s, 'stream wrong CN'); +like(stream("127.0.0.1:$p4")->io('GOdefaul.example.com'), + qr/connect failed/s, 'stream verify_depth too small'); + +############################################################################### + +sub reply_handler { + my ($recv_data, $port, %extra) = @_; + + my (@name, @rdata); + + use constant NOERROR => 0; + use constant A => 1; + use constant IN => 1; + + # default values + + my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 3600); + + # decode name + + my ($len, $offset) = (undef, 12); + while (1) { + $len = unpack("\@$offset C", $recv_data); + last if $len == 0; + $offset++; + push @name, unpack("\@$offset A$len", $recv_data); + $offset += $len; + } + + $offset -= 1; + my ($id, $type, $class) = unpack("n x$offset n2", $recv_data); + + my $name = join('.', @name); + + if ($type == A) { + push @rdata, rd_addr($ttl, '127.0.0.1'); + } + + $len = @name; + pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata, + 0, 0, @name, $type, $class) . join('', @rdata); +} + +sub rd_addr { + my ($ttl, $addr) = @_; + + my $code = 'split(/\./, $addr)'; + + return pack 'n3N', 0xc00c, A, IN, $ttl if $addr eq ''; + + pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code); +} + +sub dns_daemon { + my ($port, $t) = @_; + + my ($data, $recv_data); + my $socket = IO::Socket::INET->new( + LocalAddr => '127.0.0.1', + LocalPort => $port, + Proto => 'udp', + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + # signal we are ready + + open my $fh, '>', $t->testdir() . '/' . $port; + close $fh; + + while (1) { + $socket->recv($recv_data, 65536); + $data = reply_handler($recv_data, $port); + $socket->send($data); + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_js_import.t b/tests/nginx-tests/nginx-tests/stream_js_import.t new file mode 100644 index 0000000000..7000060409 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_js_import.t @@ -0,0 +1,117 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for stream njs module, js_import directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http stream stream_return/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_set $test foo.bar.p; + + js_import lib.js; + js_import foo from ./main.js; + + server { + listen 127.0.0.1:8081; + return $test; + } + + server { + listen 127.0.0.1:8082; + js_access lib.access; + js_preread lib.preread; + js_filter lib.filter; + proxy_pass 127.0.0.1:8083; + } + + server { + listen 127.0.0.1:8083; + return "x"; + } +} + +EOF + +$t->write_file('lib.js', <= 3) { + s.done(); + } + }); + } + + function filter(s) { + s.on('upload', function(data, flags) { + s.send(data); + res += '3'; + }); + + s.on('download', function(data, flags) { + if (!flags.last) { + res += '4'; + s.send(data); + + } else { + res += '5'; + s.send(res, {last:1}); + s.off('download'); + } + }); + } + + export default {access, preread, filter}; + +EOF + +$t->write_file('main.js', <try_run('no njs available')->plan(2); + +############################################################################### + +is(stream('127.0.0.1:' . port(8081))->read(), 'P-TEST', 'foo.bar.p'); +is(stream('127.0.0.1:' . port(8082))->io('0'), 'x122345', 'lib.access'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_js_import2.t b/tests/nginx-tests/nginx-tests/stream_js_import2.t new file mode 100644 index 0000000000..a057c25817 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_js_import2.t @@ -0,0 +1,117 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for stream njs module, js_import directive in server context. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream stream_return/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + server { + listen 127.0.0.1:8081; + js_import foo from ./main.js; + js_set $test foo.bar.p; + return $test; + } + + server { + listen 127.0.0.1:8082; + + js_import lib.js; + + js_access lib.access; + js_preread lib.preread; + js_filter lib.filter; + proxy_pass 127.0.0.1:8083; + } + + server { + listen 127.0.0.1:8083; + return "x"; + } +} + +EOF + +$t->write_file('lib.js', <= 3) { + s.done(); + } + }); + } + + function filter(s) { + s.on('upload', function(data, flags) { + s.send(data); + res += '3'; + }); + + s.on('download', function(data, flags) { + if (!flags.last) { + res += '4'; + s.send(data); + + } else { + res += '5'; + s.send(res, {last:1}); + s.off('download'); + } + }); + } + + export default {access, preread, filter}; + +EOF + +$t->write_file('main.js', <try_run('no njs available')->plan(2); + +############################################################################### + +is(stream('127.0.0.1:' . port(8081))->read(), 'P-TEST', 'foo.bar.p'); +is(stream('127.0.0.1:' . port(8082))->io('0'), 'x122345', 'lib.access'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_js_ngx.t b/tests/nginx-tests/nginx-tests/stream_js_ngx.t new file mode 100644 index 0000000000..b99cd2eec0 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_js_ngx.t @@ -0,0 +1,98 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for stream njs module, ngx object. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream stream_return/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + } +} + +stream { + js_import test.js; + + js_set $log test.log; + + server { + listen 127.0.0.1:8081; + return $log; + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs ngx')->plan(4); + +############################################################################### + +TODO: { +local $TODO = 'not yet' + unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.5.0'; + +is(stream('127.0.0.1:' . port(8081))->read(), 'OK', 'log var'); + +$t->stop(); + +like($t->read_file('error.log'), qr/\[info\].*ngx.log:FOO/, 'ngx.log info'); +like($t->read_file('error.log'), qr/\[warn\].*ngx.log:BAR/, 'ngx.log warn'); +like($t->read_file('error.log'), qr/\[error\].*ngx.log:BAZ/, 'ngx.log err'); + +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_js_object.t b/tests/nginx-tests/nginx-tests/stream_js_object.t new file mode 100644 index 0000000000..504b9348d8 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_js_object.t @@ -0,0 +1,98 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for stream njs module, stream session object. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream stream_return/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_set $test test.test; + + js_import test.js; + + server { + listen 127.0.0.1:8081; + return $test$status; + } +} + +EOF + +$t->write_file('test.js', <v in s.variables) + .toString() === 'true,false'; + } + + function redefine_proto(s) { + s[0] = 'a'; + s[1] = 'b'; + s.length = 2; + Object.setPrototypeOf(s, Array.prototype); + return s.join('|') === 'a|b'; + } + + function get_own_prop_descs(s) { + return Object.getOwnPropertyDescriptors(s)['on'].value === s.on; + } + + function test(s) { + return [ to_string, + define_prop, + in_operator, + redefine_proto, + get_own_prop_descs, + ].every(v=>v(s)); + } + + export default {test}; + +EOF + +$t->try_run('no njs stream session object')->plan(1); + +############################################################################### + +is(stream('127.0.0.1:' . port(8081))->read(), 'true400', 'var set'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_js_preload_object.t b/tests/nginx-tests/nginx-tests/stream_js_preload_object.t new file mode 100644 index 0000000000..c28c401ad7 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_js_preload_object.t @@ -0,0 +1,122 @@ +#!/usr/bin/perl + +# (C) Vadim Zhestikov +# (C) Nginx, Inc. + +# Tests for stream njs module, js_preload_object directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream stream_return/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_preload_object g1 from g.json; + + js_set $test foo.bar.p; + + js_import lib.js; + js_import foo from ./main.js; + + server { + listen 127.0.0.1:8081; + return $test; + } + + server { + listen 127.0.0.1:8082; + js_access lib.access; + js_preread lib.preread; + js_filter lib.filter; + proxy_pass 127.0.0.1:8083; + } + + server { + listen 127.0.0.1:8083; + return "x"; + } +} + +EOF + +$t->write_file('lib.js', <= 3) { + s.done(); + } + }); + } + + function filter(s) { + s.on('upload', function(data, flags) { + s.send(data); + res += g1.c.prop[0].a; + }); + + s.on('download', function(data, flags) { + if (!flags.last) { + res += g1.b[3]; + s.send(data); + + } else { + res += g1.b[4]; + s.send(res, {last:1}); + s.off('download'); + } + }); + } + + export default {access, preread, filter}; + +EOF + +$t->write_file('main.js', <write_file('g.json', + '{"a":1, "b":[1,2,"element",4,5], "c":{"prop":[{"a":3}]}}'); + +$t->try_run('no js_preload_object available')->plan(2); + +############################################################################### + +is(stream('127.0.0.1:' . port(8081))->read(), 'element', 'foo.bar.p'); +is(stream('127.0.0.1:' . port(8082))->io('0'), 'x122345', 'lib.access'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_js_var.t b/tests/nginx-tests/nginx-tests/stream_js_var.t new file mode 100644 index 0000000000..cde2dc9f4c --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_js_var.t @@ -0,0 +1,75 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for stream njs module, js_var directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream stream_return/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_import test.js; + + js_var $foo; + js_var $bar a:$remote_addr; + js_set $var test.varr; + + server { + listen 127.0.0.1:8081; + return $bar$foo; + } + + server { + listen 127.0.0.1:8082; + return $var$foo; + } +} + +EOF + +$t->write_file('test.js', <try_run('no stream js_var')->plan(2); + +############################################################################### + +is(stream('127.0.0.1:' . port(8081))->io('###'), 'a:127.0.0.1', + 'default value'); +is(stream('127.0.0.1:' . port(8082))->io('###'), 'xxx', 'value set'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_js_var2.t b/tests/nginx-tests/nginx-tests/stream_js_var2.t new file mode 100644 index 0000000000..71cee1e38d --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_js_var2.t @@ -0,0 +1,75 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for stream njs module, js_var directive in server context. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream stream_return/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_import test.js; + + js_var $foo; + + server { + listen 127.0.0.1:8081; + js_var $bar a:$remote_addr; + return $bar$foo; + } + + server { + listen 127.0.0.1:8082; + js_set $var test.varr; + return $var$foo; + } +} + +EOF + +$t->write_file('test.js', <try_run('no stream js_var')->plan(2); + +############################################################################### + +is(stream('127.0.0.1:' . port(8081))->io('###'), 'a:127.0.0.1', + 'default value'); +is(stream('127.0.0.1:' . port(8082))->io('###'), 'xxx', 'value set'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_js_variables.t b/tests/nginx-tests/nginx-tests/stream_js_variables.t new file mode 100644 index 0000000000..29e6c33ee1 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_js_variables.t @@ -0,0 +1,84 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for stream njs module, setting nginx variables. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream stream_return/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_set $test_var test.variable; + js_set $test_not_found test.not_found; + + js_import test.js; + + server { + listen 127.0.0.1:8081; + return $test_var$status; + } + + server { + listen 127.0.0.1:8082; + return $test_not_found; + } +} + +EOF + +$t->write_file('test.js', <try_run('no stream njs available')->plan(2); + +############################################################################### + +is(stream('127.0.0.1:' . port(8081))->read(), 'test_var400', 'var set'); +is(stream('127.0.0.1:' . port(8082))->read(), 'not_found', 'not found set'); + +$t->stop(); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_limit_conn.t b/tests/nginx-tests/nginx-tests/stream_limit_conn.t index 9804e2cd59..72185b318b 100644 --- a/tests/nginx-tests/nginx-tests/stream_limit_conn.t +++ b/tests/nginx-tests/nginx-tests/stream_limit_conn.t @@ -34,6 +34,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + limit_conn_zone $binary_remote_addr zone=zone:1m; limit_conn_zone $binary_remote_addr zone=zone2:1m; diff --git a/tests/nginx-tests/nginx-tests/stream_limit_conn_complex.t b/tests/nginx-tests/nginx-tests/stream_limit_conn_complex.t index 96a2169a79..e4cf816f8d 100644 --- a/tests/nginx-tests/nginx-tests/stream_limit_conn_complex.t +++ b/tests/nginx-tests/nginx-tests/stream_limit_conn_complex.t @@ -34,6 +34,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + limit_conn_zone $binary_remote_addr$server_port zone=zone:1m; server { diff --git a/tests/nginx-tests/nginx-tests/stream_limit_conn_dry_run.t b/tests/nginx-tests/nginx-tests/stream_limit_conn_dry_run.t new file mode 100644 index 0000000000..ca7e52824c --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_limit_conn_dry_run.t @@ -0,0 +1,112 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for limit_conn_dry_run directive, limit_conn_status variable. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream stream_limit_conn http/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + limit_conn_zone $binary_remote_addr zone=zone:1m; + + log_format test $server_port:$limit_conn_status; + access_log %%TESTDIR%%/test.log test; + + server { + listen 127.0.0.1:8080; + proxy_pass 127.0.0.1:8084; + limit_conn zone 1; + + proxy_timeout 5s; + } + + server { + listen 127.0.0.1:8081; + proxy_pass 127.0.0.1:8084; + limit_conn zone 1; + } + + server { + listen 127.0.0.1:8082; + proxy_pass 127.0.0.1:8084; + limit_conn zone 1; + + limit_conn_dry_run on; + } + + server { + listen 127.0.0.1:8083; + proxy_pass 127.0.0.1:8084; + } +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8084; + server_name localhost; + + location / { } + } +} + +EOF + +$t->write_file('index.html', 'OK'); +$t->run()->plan(9); + +############################################################################### + +my ($p, $p1, $p2, $p3) = (port(8080), port(8081), port(8082), port(8083)); + +is(stream("127.0.0.1:$p")->io("GET /\n"), 'OK', 'passed'); + +my $s = stream('127.0.0.1:' . port(8080)); +$s->write("GET"); + +is(stream("127.0.0.1:$p1")->io("GET /\n"), '', 'rejected'); +is(stream("127.0.0.1:$p2")->io("GET /\n"), 'OK', 'rejected dry run'); +is(stream("127.0.0.1:$p3")->io("GET /\n"), 'OK', 'no limit'); + +undef $s; + +$t->stop(); + +like($t->read_file('error.log'), qr/limiting connections, dry/, 'log dry run'); +like($t->read_file('test.log'), qr|$p:PASSED|, 'log passed'); +like($t->read_file('test.log'), qr|$p1:REJECTED$|m, 'log rejected'); +like($t->read_file('test.log'), qr|$p2:REJECTED_DRY_RUN|, 'log rejected dry'); +like($t->read_file('test.log'), qr|$p3:-|, 'log not found'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_limit_rate.t b/tests/nginx-tests/nginx-tests/stream_limit_rate.t index ecc9a0f9bb..86d6487a13 100644 --- a/tests/nginx-tests/nginx-tests/stream_limit_rate.t +++ b/tests/nginx-tests/nginx-tests/stream_limit_rate.t @@ -25,7 +25,7 @@ use Test::Nginx::Stream qw/ stream /; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/stream/)->plan(8) +my $t = Test::Nginx->new()->has(qw/stream/)->plan(9) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -36,6 +36,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + # download and upload rates are set equal to the maximum # number of bytes transmitted @@ -98,15 +100,18 @@ my $str = '1234567890' x 100; my %r = response($str, peer => '127.0.0.1:' . port(8081)); is($r{'data'}, $str, 'exact limit'); -%r = response($str, peer => '127.0.0.1:' . port(8082)); -is($r{'data'}, $str, 'unlimited'); +%r = response($str . 'extra', peer => '127.0.0.1:' . port(8082)); +is($r{'data'}, $str . 'extra', 'unlimited'); SKIP: { -skip 'unsafe on VM', 2 unless $ENV{TEST_NGINX_UNSAFE}; +skip 'unsafe on VM', 3 unless $ENV{TEST_NGINX_UNSAFE}; # if interaction between backend and client is slow then proxy can add extra # bytes to upload/download data +%r = response($str . 'extra', peer => '127.0.0.1:' . port(8081)); +is($r{'data'}, $str, 'limited'); + %r = response($str, peer => '127.0.0.1:' . port(8083), readonce => 1); is($r{'data'}, '1', 'download - one byte'); diff --git a/tests/nginx-tests/nginx-tests/stream_limit_rate2.t b/tests/nginx-tests/nginx-tests/stream_limit_rate2.t new file mode 100644 index 0000000000..963581090c --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_limit_rate2.t @@ -0,0 +1,229 @@ +#!/usr/bin/perl + +# (C) Andrey Zelenkov +# (C) Nginx, Inc. + +# Tests for stream proxy module, limit rate directives, variables support. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use IO::Select; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream stream_map/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + # download and upload rates are set equal to the maximum + # number of bytes transmitted + + # proxy_download_rate value comes from following calculations: + # test string length (1000) + whitespace (1) + time string length (10) + + map $server_port $down { + default 1011; + %%PORT_8082%% 0; + %%PORT_8083%% 1; + %%PORT_8085%% 250; + } + + map $server_port $up { + default 1000; + %%PORT_8082%% 0; + %%PORT_8084%% 1; + %%PORT_8086%% 250; + } + + proxy_download_rate $down; + proxy_upload_rate $up; + + server { + listen 127.0.0.1:8081; + proxy_pass 127.0.0.1:8080; + } + + server { + listen 127.0.0.1:8082; + proxy_pass 127.0.0.1:8080; + proxy_download_rate $down; + proxy_upload_rate $up; + } + + server { + listen 127.0.0.1:8083; + proxy_pass 127.0.0.1:8080; + proxy_download_rate $down; + } + + server { + listen 127.0.0.1:8084; + proxy_pass 127.0.0.1:8080; + proxy_upload_rate $up; + } + + server { + listen 127.0.0.1:8085; + proxy_pass 127.0.0.1:8080; + proxy_download_rate $down; + } + + server { + listen 127.0.0.1:8086; + proxy_pass 127.0.0.1:8087; + proxy_upload_rate $up; + } +} + +EOF + +$t->run_daemon(\&stream_daemon, port(8080)); +$t->run_daemon(\&stream_daemon, port(8087)); +$t->run()->plan(9); + +$t->waitforsocket('127.0.0.1:' . port(8080)); +$t->waitforsocket('127.0.0.1:' . port(8087)); + +############################################################################### + +my $str = '1234567890' x 100; + +my %r = response($str, peer => '127.0.0.1:' . port(8081)); +is($r{'data'}, $str, 'exact limit'); + +%r = response($str . 'extra', peer => '127.0.0.1:' . port(8082)); +is($r{'data'}, $str . 'extra', 'unlimited'); + +SKIP: { +skip 'unsafe on VM', 3 unless $ENV{TEST_NGINX_UNSAFE}; + +# if interaction between backend and client is slow then proxy can add extra +# bytes to upload/download data + +%r = response($str . 'extra', peer => '127.0.0.1:' . port(8081)); +is($r{'data'}, $str, 'limited'); + +%r = response($str, peer => '127.0.0.1:' . port(8083), readonce => 1); +is($r{'data'}, '1', 'download - one byte'); + +%r = response($str, peer => '127.0.0.1:' . port(8084)); +is($r{'data'}, '1', 'upload - one byte'); + +} + +# Five chunks are split with four 1s delays: +# the first four chunks are quarters of test string +# and the fifth one is some extra data from backend. + +%r = response($str, peer => '127.0.0.1:' . port(8085)); +my $diff = time() - $r{'time'}; +cmp_ok($diff, '>=', 4, 'download - time'); +is($r{'data'}, $str, 'download - data'); + +my $time = time(); +%r = response($str . 'close', peer => '127.0.0.1:' . port(8086)); +$diff = time() - $time; +cmp_ok($diff, '>=', 4, 'upload - time'); +is($r{'data'}, $str . 'close', 'upload - data'); + +############################################################################### + +sub response { + my ($data, %extra) = @_; + + my $s = stream($extra{peer}); + $s->write($data); + + $data = ''; + while (1) { + my $buf = $s->read(); + last unless length($buf); + + $data .= $buf; + + last if $extra{'readonce'}; + } + $data =~ /([\S]*)\s?(\d+)?/; + + return ('data' => $1, 'time' => $2) +} + +############################################################################### + +sub stream_daemon { + my $port = shift; + + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1', + LocalPort => $port, + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + my $sel = IO::Select->new($server); + + local $SIG{PIPE} = 'IGNORE'; + + while (my @ready = $sel->can_read) { + foreach my $fh (@ready) { + if ($server == $fh) { + my $new = $fh->accept; + $new->autoflush(1); + $sel->add($new); + + } elsif (stream_handle_client($fh)) { + $sel->remove($fh); + $fh->close; + } + } + } +} + +sub stream_handle_client { + my ($client) = @_; + + log2c("(new connection $client)"); + + $client->sysread(my $buffer, 65536) or return 1; + + log2i("$client $buffer"); + + $buffer .= " " . time() if $client->sockport() eq port(8080); + + log2o("$client $buffer"); + + $client->syswrite($buffer); + + return $client->sockport() eq port(8080) ? 1 : $buffer =~ /close/; +} + +sub log2i { Test::Nginx::log_core('|| <<', @_); } +sub log2o { Test::Nginx::log_core('|| >>', @_); } +sub log2c { Test::Nginx::log_core('||', @_); } + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_map.t b/tests/nginx-tests/nginx-tests/stream_map.t index 35a46c905b..3b157cbdde 100644 --- a/tests/nginx-tests/nginx-tests/stream_map.t +++ b/tests/nginx-tests/nginx-tests/stream_map.t @@ -36,6 +36,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + map $server_port $x { %%PORT_8080%% literal; default default; diff --git a/tests/nginx-tests/nginx-tests/stream_proxy.t b/tests/nginx-tests/nginx-tests/stream_proxy.t index ebf1237694..da324901bc 100644 --- a/tests/nginx-tests/nginx-tests/stream_proxy.t +++ b/tests/nginx-tests/nginx-tests/stream_proxy.t @@ -36,6 +36,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + server { listen 127.0.0.1:8080; proxy_pass 127.0.0.1:8081; diff --git a/tests/nginx-tests/nginx-tests/stream_proxy_bind.t b/tests/nginx-tests/nginx-tests/stream_proxy_bind.t index 0a7112392c..7404b8597b 100644 --- a/tests/nginx-tests/nginx-tests/stream_proxy_bind.t +++ b/tests/nginx-tests/nginx-tests/stream_proxy_bind.t @@ -37,6 +37,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + server { listen 127.0.0.1:8081; proxy_bind 127.0.0.2; diff --git a/tests/nginx-tests/nginx-tests/stream_proxy_complex.t b/tests/nginx-tests/nginx-tests/stream_proxy_complex.t index 15fa8c6f57..da4ac31172 100644 --- a/tests/nginx-tests/nginx-tests/stream_proxy_complex.t +++ b/tests/nginx-tests/nginx-tests/stream_proxy_complex.t @@ -35,6 +35,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + upstream %%PORT_8081%% { server 127.0.0.1:8091; } diff --git a/tests/nginx-tests/nginx-tests/stream_proxy_half_close.t b/tests/nginx-tests/nginx-tests/stream_proxy_half_close.t new file mode 100644 index 0000000000..a265643f16 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_proxy_half_close.t @@ -0,0 +1,117 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for stream proxy_half_close directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use IO::Select; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + server { + listen 127.0.0.1:8080; + proxy_pass 127.0.0.1:8081; + + proxy_half_close on; + } +} + +EOF + +$t->try_run('no proxy_half_close')->plan(2); + +############################################################################### + +my ($s, $u) = pair(8080, 8081); +shutdown($u, 1); +is(proxy($s, $u, 'SEE'), 'SEE', 'half close upstream'); + +($s, $u) = pair(8080, 8081); +shutdown($s, 1); +is(proxy($u, $s, 'SEE'), 'SEE', 'half close client'); + +############################################################################### + +sub pair { + my ($server, $backend) = @_; + + my $listen = IO::Socket::INET->new( + LocalHost => '127.0.0.1:' . port($backend), + Listen => 5, + Reuse => 1, + ) + or die "Can't listen on $server: $!\n"; + + my $connect = IO::Socket::INET->new( + Proto => 'tcp', + PeerHost => '127.0.0.1:' . port($server), + ) + or die "Can't connect to $server: $!\n"; + + my $accept = $listen->accept() if IO::Select->new($listen)->can_read(3); + + return $connect, $accept; +} + +sub proxy { + my ($from, $to, $msg) = @_; + proxy_from($from, $msg); + return proxy_to($to); +} + +sub proxy_from { + my ($s, $msg) = @_; + + local $SIG{PIPE} = 'IGNORE'; + + while (IO::Select->new($s)->can_write(5)) { + my $n = $s->syswrite($msg); + log_out(substr($msg, 0, $n)); + last unless $n; + + $msg = substr($msg, $n); + last unless length $msg; + } +} + +sub proxy_to { + my ($s) = @_; + my $buf; + + $s->sysread($buf, 1024) if IO::Select->new($s)->can_read(5); + + log_in($buf); + return $buf; +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_proxy_next_upstream.t b/tests/nginx-tests/nginx-tests/stream_proxy_next_upstream.t index fc42950d24..7e6a231ac8 100644 --- a/tests/nginx-tests/nginx-tests/stream_proxy_next_upstream.t +++ b/tests/nginx-tests/nginx-tests/stream_proxy_next_upstream.t @@ -23,7 +23,7 @@ use Test::Nginx::Stream qw/ stream /; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/stream/)->plan(3); +my $t = Test::Nginx->new()->has(qw/stream/)->plan(5); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -35,6 +35,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + upstream u { server 127.0.0.1:8083 max_fails=0; server 127.0.0.1:8084 max_fails=0; @@ -46,7 +48,12 @@ stream { server 127.0.0.1:8085 backup; } - proxy_connect_timeout 1s; + upstream u3 { + server 127.0.0.1:8083; + server 127.0.0.1:8085 down; + } + + proxy_connect_timeout 2; server { listen 127.0.0.1:8080; @@ -66,6 +73,15 @@ stream { proxy_next_upstream on; proxy_next_upstream_tries 2; } + + log_format test "$upstream_addr"; + + server { + listen 127.0.0.1:8086; + proxy_pass u3; + proxy_next_upstream on; + access_log %%TESTDIR%%/test.log test; + } } EOF @@ -82,6 +98,15 @@ is(stream('127.0.0.1:' . port(8081))->io('.'), 'SEE-THIS', 'next on'); is(stream('127.0.0.1:' . port(8082))->io('.'), '', 'next tries'); +# make sure backend marked as down doesn't count towards "no live upstreams" + +is(stream('127.0.0.1:' . port(8086))->io('.'), '', 'next down'); + +$t->stop(); + +is($t->read_file('test.log'), '127.0.0.1:' . port(8083) . "\n", + 'next down log'); + ############################################################################### sub stream_daemon { diff --git a/tests/nginx-tests/nginx-tests/stream_proxy_protocol.t b/tests/nginx-tests/nginx-tests/stream_proxy_protocol.t index 2045ddc13e..74da547c1b 100644 --- a/tests/nginx-tests/nginx-tests/stream_proxy_protocol.t +++ b/tests/nginx-tests/nginx-tests/stream_proxy_protocol.t @@ -37,6 +37,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_protocol on; server { diff --git a/tests/nginx-tests/nginx-tests/stream_proxy_protocol_ipv6.t b/tests/nginx-tests/nginx-tests/stream_proxy_protocol_ipv6.t index 1c83cd63ea..13620a2ba3 100644 --- a/tests/nginx-tests/nginx-tests/stream_proxy_protocol_ipv6.t +++ b/tests/nginx-tests/nginx-tests/stream_proxy_protocol_ipv6.t @@ -37,6 +37,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + server { listen 127.0.0.1:8080; proxy_pass [::1]:%%PORT_8080%%; diff --git a/tests/nginx-tests/nginx-tests/stream_proxy_protocol_ssl.t b/tests/nginx-tests/nginx-tests/stream_proxy_protocol_ssl.t index 141f181c68..24ee5dd6a9 100644 --- a/tests/nginx-tests/nginx-tests/stream_proxy_protocol_ssl.t +++ b/tests/nginx-tests/nginx-tests/stream_proxy_protocol_ssl.t @@ -40,6 +40,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_ssl on; proxy_protocol on; diff --git a/tests/nginx-tests/nginx-tests/stream_proxy_ssl.t b/tests/nginx-tests/nginx-tests/stream_proxy_ssl.t index 66a229be37..3aec564ba0 100644 --- a/tests/nginx-tests/nginx-tests/stream_proxy_ssl.t +++ b/tests/nginx-tests/nginx-tests/stream_proxy_ssl.t @@ -39,6 +39,8 @@ events { worker_processes 1; # NOTE: The default value of Tengine worker_processes directive is `worker_processes auto;`. stream { + %%TEST_GLOBALS_STREAM%% + proxy_ssl on; proxy_ssl_session_reuse on; proxy_connect_timeout 2s; diff --git a/tests/nginx-tests/nginx-tests/stream_proxy_ssl_certificate.t b/tests/nginx-tests/nginx-tests/stream_proxy_ssl_certificate.t index ee2e0d501d..973f08a439 100644 --- a/tests/nginx-tests/nginx-tests/stream_proxy_ssl_certificate.t +++ b/tests/nginx-tests/nginx-tests/stream_proxy_ssl_certificate.t @@ -36,6 +36,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_ssl on; proxy_ssl_session_reuse off; diff --git a/tests/nginx-tests/nginx-tests/stream_proxy_ssl_certificate_vars.t b/tests/nginx-tests/nginx-tests/stream_proxy_ssl_certificate_vars.t new file mode 100644 index 0000000000..83b9bca24b --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_proxy_ssl_certificate_vars.t @@ -0,0 +1,162 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for stream proxy module with variables in ssl certificates. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream stream_ssl stream_map http http_ssl/) + ->has_daemon('openssl'); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + map $server_port $cert { + %%PORT_8082%% 1; + %%PORT_8083%% 2; + %%PORT_8084%% 3; + %%PORT_8085%% ""; + } + + proxy_ssl on; + proxy_ssl_session_reuse off; + + server { + listen 127.0.0.1:8082; + listen 127.0.0.1:8083; + proxy_pass 127.0.0.1:8080; + + proxy_ssl_certificate $cert.example.com.crt; + proxy_ssl_certificate_key $cert.example.com.key; + } + + server { + listen 127.0.0.1:8084; + proxy_pass 127.0.0.1:8081; + + proxy_ssl_certificate $cert.example.com.crt; + proxy_ssl_certificate_key $cert.example.com.key; + proxy_ssl_password_file password; + } + + server { + listen 127.0.0.1:8085; + proxy_pass 127.0.0.1:8081; + + proxy_ssl_certificate $cert; + proxy_ssl_certificate_key $cert; + } +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080 ssl; + server_name localhost; + + ssl_certificate 2.example.com.crt; + ssl_certificate_key 2.example.com.key; + + ssl_verify_client optional_no_ca; + ssl_trusted_certificate 1.example.com.crt; + + location / { + add_header X-Verify $ssl_client_verify; + add_header X-Name $ssl_client_s_dn; + } + } + + server { + listen 127.0.0.1:8081 ssl; + server_name localhost; + + ssl_certificate 1.example.com.crt; + ssl_certificate_key 1.example.com.key; + + ssl_verify_client optional_no_ca; + ssl_trusted_certificate 3.example.com.crt; + + location / { + add_header X-Verify $ssl_client_verify; + } + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('1.example.com', '2.example.com') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +foreach my $name ('3.example.com') { + system("openssl genrsa -out $d/$name.key -passout pass:$name " + . "-aes128 2048 >>$d/openssl.out 2>&1") == 0 + or die "Can't create private key: $!\n"; + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt " + . "-key $d/$name.key -passin pass:$name" + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +sleep 1 if $^O eq 'MSWin32'; + +$t->write_file('password', '3.example.com'); +$t->write_file('index.html', ''); + +$t->try_run('no upstream ssl_certificate variables')->plan(4); + +############################################################################### + +like(http_get('/', socket => IO::Socket::INET->new('127.0.0.1:' . port(8082))), + qr/X-Verify: SUCCESS/ms, 'variable - verify certificate'); +like(http_get('/', socket => IO::Socket::INET->new('127.0.0.1:' . port(8083))), + qr/X-Verify: FAILED/ms, 'variable - fail certificate'); +like(http_get('/', socket => IO::Socket::INET->new('127.0.0.1:' . port(8084))), + qr/X-Verify: SUCCESS/ms, 'variable - with encrypted key'); +like(http_get('/', socket => IO::Socket::INET->new('127.0.0.1:' . port(8085))), + qr/X-Verify: NONE/ms, 'variable - no certificate'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_proxy_ssl_conf_command.t b/tests/nginx-tests/nginx-tests/stream_proxy_ssl_conf_command.t new file mode 100644 index 0000000000..542d9a2bd3 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_proxy_ssl_conf_command.t @@ -0,0 +1,98 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for stream proxy to ssl backend, proxy_ssl_conf_command. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream stream_ssl http http_ssl/) + ->has_daemon('openssl'); + +$t->{_configure_args} =~ /OpenSSL ([\d\.]+)/; +plan(skip_all => 'OpenSSL too old') unless defined $1 and $1 ge '1.0.2'; +plan(skip_all => 'no ssl_conf_command') if $t->has_module('BoringSSL'); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + server { + listen 127.0.0.1:8080; + proxy_pass 127.0.0.1:8081; + proxy_ssl on; + + proxy_ssl_certificate localhost.crt; + proxy_ssl_certificate_key localhost.key; + proxy_ssl_conf_command Certificate override.crt; + proxy_ssl_conf_command PrivateKey override.key; + } +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8081 ssl; + server_name localhost; + + ssl_certificate localhost.crt; + ssl_certificate_key localhost.key; + ssl_verify_client optional_no_ca; + + add_header X-Cert $ssl_client_s_dn always; + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('localhost', 'override') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->write_file('index.html', ''); +$t->run()->plan(1); + +############################################################################### + +like(http_get('/'), qr/CN=override/, 'proxy_ssl_conf_command'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_proxy_ssl_name.t b/tests/nginx-tests/nginx-tests/stream_proxy_ssl_name.t index 304a5788cd..0993c473bc 100644 --- a/tests/nginx-tests/nginx-tests/stream_proxy_ssl_name.t +++ b/tests/nginx-tests/nginx-tests/stream_proxy_ssl_name.t @@ -36,6 +36,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_ssl on; proxy_ssl_session_reuse off; diff --git a/tests/nginx-tests/nginx-tests/stream_proxy_ssl_name_complex.t b/tests/nginx-tests/nginx-tests/stream_proxy_ssl_name_complex.t index 47b317f621..748d6bd43b 100644 --- a/tests/nginx-tests/nginx-tests/stream_proxy_ssl_name_complex.t +++ b/tests/nginx-tests/nginx-tests/stream_proxy_ssl_name_complex.t @@ -37,6 +37,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_ssl on; proxy_ssl_session_reuse off; diff --git a/tests/nginx-tests/nginx-tests/stream_proxy_ssl_verify.t b/tests/nginx-tests/nginx-tests/stream_proxy_ssl_verify.t index 656785fc99..697e058f3a 100644 --- a/tests/nginx-tests/nginx-tests/stream_proxy_ssl_verify.t +++ b/tests/nginx-tests/nginx-tests/stream_proxy_ssl_verify.t @@ -16,15 +16,17 @@ BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; ############################################################################### select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/stream stream_ssl/)->has_daemon('openssl'); +my $t = Test::Nginx->new()->has(qw/stream stream_ssl stream_return/) + ->has_daemon('openssl')->plan(6); -$t->write_file_expand('nginx.conf', <<'EOF')->plan(6); +$t->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -34,6 +36,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_ssl on; proxy_ssl_verify on; @@ -87,8 +91,8 @@ stream { server { listen 127.0.0.1:8086 ssl; - proxy_pass 127.0.0.1:8088; proxy_ssl off; + return OK; ssl_certificate 1.example.com.crt; ssl_certificate_key 1.example.com.key; @@ -96,8 +100,8 @@ stream { server { listen 127.0.0.1:8087 ssl; - proxy_pass 127.0.0.1:8088; proxy_ssl off; + return OK; ssl_certificate 2.example.com.crt; ssl_certificate_key 2.example.com.key; @@ -144,73 +148,29 @@ foreach my $name ('1.example.com', '2.example.com') { sleep 1 if $^O eq 'MSWin32'; -$t->write_file('index.html', ''); - -$t->run_daemon(\&http_daemon); $t->run(); -$t->waitforsocket('127.0.0.1:' . port(8088)); - ############################################################################### # subjectAltName -like(get('/', '127.0.0.1:' . port(8080)), qr/200 OK/, 'verify'); -like(get('/', '127.0.0.1:' . port(8081)), qr/200 OK/, 'verify wildcard'); -unlike(get('/', '127.0.0.1:' . port(8082)), qr/200 OK/, 'verify fail'); +is(get(8080), 'OK', 'verify'); +is(get(8081), 'OK', 'verify wildcard'); +isnt(get(8082), 'OK', 'verify fail'); # commonName -like(get('/', '127.0.0.1:' . port(8083)), qr/200 OK/, 'verify cn'); -unlike(get('/', '127.0.0.1:' . port(8084)), qr/200 OK/, 'verify cn fail'); +is(get(8083), 'OK', 'verify cn'); +isnt(get(8084), 'OK', 'verify cn fail'); # untrusted -unlike(get('/', '127.0.0.1:' . port(8085)), qr/200 OK/, 'untrusted'); +isnt(get(8085), 'OK', 'untrusted'); ############################################################################### sub get { - my ($uri, $peer) = @_; - - my $s = IO::Socket::INET->new( - Proto => 'tcp', - PeerAddr => $peer - ) - or die "Can't connect to nginx: $!\n"; - - my $r = http_get($uri, socket => $s); - return defined $r ? $r : ''; -} - -############################################################################### - -sub http_daemon { - my $server = IO::Socket::INET->new( - Proto => 'tcp', - LocalHost => '127.0.0.1:' . port(8088), - Listen => 5, - Reuse => 1 - ) - or die "Can't create listening socket: $!\n"; - - local $SIG{PIPE} = 'IGNORE'; - - while (my $client = $server->accept()) { - $client->autoflush(1); - - while (<$client>) { - last if (/^\x0d?\x0a?$/); - } - - print $client <read(); } ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_realip.t b/tests/nginx-tests/nginx-tests/stream_realip.t index 759a8a7f3e..2a21c63d7c 100644 --- a/tests/nginx-tests/nginx-tests/stream_realip.t +++ b/tests/nginx-tests/nginx-tests/stream_realip.t @@ -37,6 +37,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + server { listen 127.0.0.1:8083 proxy_protocol; listen 127.0.0.1:8084; diff --git a/tests/nginx-tests/nginx-tests/stream_realip_hostname.t b/tests/nginx-tests/nginx-tests/stream_realip_hostname.t index a867ba3923..e2b5bfcf3e 100644 --- a/tests/nginx-tests/nginx-tests/stream_realip_hostname.t +++ b/tests/nginx-tests/nginx-tests/stream_realip_hostname.t @@ -36,6 +36,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + server { listen unix:%%TESTDIR%%/unix.sock proxy_protocol; listen 127.0.0.1:8080; diff --git a/tests/nginx-tests/nginx-tests/stream_resolver.t b/tests/nginx-tests/nginx-tests/stream_resolver.t index 3613521e7b..4a10c8633a 100644 --- a/tests/nginx-tests/nginx-tests/stream_resolver.t +++ b/tests/nginx-tests/nginx-tests/stream_resolver.t @@ -34,6 +34,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + map $server_port $upstream { %%PORT_8081%% a.example.com:%%PORT_8090%%; %%PORT_8082%% a.example.com; diff --git a/tests/nginx-tests/nginx-tests/stream_set.t b/tests/nginx-tests/nginx-tests/stream_set.t new file mode 100644 index 0000000000..0b9e39f241 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_set.t @@ -0,0 +1,68 @@ +#!/usr/bin/perl + +# (C) Vladimir Kokshenev +# (C) Nginx, Inc. + +# Tests for stream set directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new() + ->has(qw/stream stream_return stream_map stream_set/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + map 0 $map_var { + default "original"; + } + + server { + listen 127.0.0.1:8082; + return $map_var:$set_var; + + set $set_var $map_var; + set $map_var "new"; + } + + server { + listen 127.0.0.1:8083; + return $set_var; + } +} + +EOF + +$t->try_run('no stream set')->plan(2); + +############################################################################### + +is(stream('127.0.0.1:' . port(8082))->read(), 'new:original', 'set'); +is(stream('127.0.0.1:' . port(8083))->read(), '', 'uninitialized variable'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_split_clients.t b/tests/nginx-tests/nginx-tests/stream_split_clients.t index 862863d35f..8b26c1663f 100644 --- a/tests/nginx-tests/nginx-tests/stream_split_clients.t +++ b/tests/nginx-tests/nginx-tests/stream_split_clients.t @@ -35,6 +35,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + split_clients $connection $variant { 51.2% "first"; 10% "second"; diff --git a/tests/nginx-tests/nginx-tests/stream_ssl.t b/tests/nginx-tests/nginx-tests/stream_ssl.t index c2cd653b13..440d8b2340 100644 --- a/tests/nginx-tests/nginx-tests/stream_ssl.t +++ b/tests/nginx-tests/nginx-tests/stream_ssl.t @@ -13,7 +13,7 @@ use strict; use Test::More; use POSIX qw/ mkfifo /; -use Socket qw/ :DEFAULT $CRLF /; +use Socket qw/ $CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -48,6 +48,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + ssl_certificate_key localhost.key; ssl_certificate localhost.crt; ssl_session_tickets off; @@ -181,14 +183,8 @@ like(Net::SSLeay::dump_peer_certificate($ssl), qr/CN=inherits/, 'CN inner'); sub get_ssl_socket { my ($port, $ses) = @_; - my $s; - - my $dest_ip = inet_aton('127.0.0.1'); - my $dest_serv_params = sockaddr_in($port, $dest_ip); - - socket($s, &AF_INET, &SOCK_STREAM, 0) or die "socket: $!"; - connect($s, $dest_serv_params) or die "connect: $!"; + my $s = IO::Socket::INET->new('127.0.0.1:' . $port); my $ssl = Net::SSLeay::new($ctx) or die("Failed to create SSL $!"); Net::SSLeay::set_session($ssl, $ses) if defined $ses; Net::SSLeay::set_fd($ssl, fileno($s)); diff --git a/tests/nginx-tests/nginx-tests/stream_ssl_alpn.t b/tests/nginx-tests/nginx-tests/stream_ssl_alpn.t new file mode 100644 index 0000000000..e6d12c8026 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_ssl_alpn.t @@ -0,0 +1,131 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for stream ssl_alpn directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream stream_ssl stream_return/) + ->has_daemon('openssl')->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + log_format test $status; + access_log %%TESTDIR%%/test.log test; + + ssl_certificate_key localhost.key; + ssl_certificate localhost.crt; + + server { + listen 127.0.0.1:8080 ssl; + return "X $ssl_alpn_protocol X"; + ssl_alpn first second; + } +} + +EOF + +eval { require IO::Socket::SSL; die if $IO::Socket::SSL::VERSION < 1.56; }; +plan(skip_all => 'IO::Socket::SSL version >= 1.56 required') if $@; + +eval { IO::Socket::SSL->can_alpn() or die; }; +plan(skip_all => 'IO::Socket::SSL with OpenSSL ALPN support required') if $@; + +eval { exists &Net::SSLeay::P_alpn_selected or die; }; +plan(skip_all => 'Net::SSLeay with OpenSSL ALPN support required') if $@; + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('localhost') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->try_run('no ssl_alpn')->plan(6); + +############################################################################### + +is(get_ssl('first'), 'X first X', 'alpn match'); +is(get_ssl('wrong', 'first'), 'X first X', 'alpn many'); +is(get_ssl('wrong', 'second'), 'X second X', 'alpn second'); +is(get_ssl(), 'X X', 'no alpn'); + +SKIP: { +$t->{_configure_args} =~ /LibreSSL ([\d\.]+)/; +skip 'LibreSSL too old', 2 if defined $1 and $1 lt '3.4.0'; +$t->{_configure_args} =~ /OpenSSL ([\d\.]+)/; +skip 'OpenSSL too old', 2 if defined $1 and $1 lt '1.1.0'; + +ok(!get_ssl('wrong'), 'alpn mismatch'); + +$t->stop(); + +like($t->read_file('test.log'), qr/500$/, 'alpn mismatch - log'); + +} + +############################################################################### + +sub get_ssl { + my (@alpn) = @_; + my $s = stream('127.0.0.1:' . port(8080)); + + eval { + local $SIG{ALRM} = sub { die "timeout\n" }; + local $SIG{PIPE} = sub { die "sigpipe\n" }; + alarm(8); + IO::Socket::SSL->start_SSL($s->{_socket}, + SSL_alpn_protocols => [ @alpn ], + SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), + SSL_error_trap => sub { die $_[1] } + ); + alarm(0); + }; + alarm(0); + + if ($@) { + log_in("died: $@"); + return undef; + } + + return $s->read(); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_ssl_conf_command.t b/tests/nginx-tests/nginx-tests/stream_ssl_conf_command.t new file mode 100644 index 0000000000..cedfd5df75 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/stream_ssl_conf_command.t @@ -0,0 +1,124 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for stream ssl module, ssl_conf_command. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { + require Net::SSLeay; + Net::SSLeay::load_error_strings(); + Net::SSLeay::SSLeay_add_ssl_algorithms(); + Net::SSLeay::randomize(); +}; +plan(skip_all => 'Net::SSLeay not installed') if $@; + +my $t = Test::Nginx->new()->has(qw/stream stream_ssl stream_return/) + ->has_daemon('openssl'); + +$t->{_configure_args} =~ /OpenSSL ([\d\.]+)/; +plan(skip_all => 'OpenSSL too old') unless defined $1 and $1 ge '1.0.2'; +plan(skip_all => 'no ssl_conf_command') if $t->has_module('BoringSSL'); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + server { + listen 127.0.0.1:8443 ssl; + return OK; + + ssl_protocols TLSv1.2; + + ssl_session_tickets off; + ssl_conf_command Options SessionTicket; + + ssl_prefer_server_ciphers on; + ssl_conf_command Options -ServerPreference; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256; + + ssl_certificate localhost.crt; + ssl_certificate_key localhost.key; + ssl_conf_command Certificate override.crt; + ssl_conf_command PrivateKey override.key; + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('localhost', 'override') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->run()->plan(3); + +############################################################################### + +my $ctx = Net::SSLeay::CTX_new() or die("Failed to create SSL_CTX $!"); + +my ($s, $ssl) = get_ssl_socket(); +like(Net::SSLeay::dump_peer_certificate($ssl), qr/CN=override/, 'Certificate'); + +my $ses = Net::SSLeay::get_session($ssl); +($s, $ssl) = get_ssl_socket(ses => $ses); +ok(Net::SSLeay::session_reused($ssl), 'SessionTicket'); + +($s, $ssl) = get_ssl_socket(ciphers => + 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384'); +is(Net::SSLeay::get_cipher($ssl), + 'ECDHE-RSA-AES128-GCM-SHA256', 'ServerPreference'); + +############################################################################### + +sub get_ssl_socket { + my (%extra) = @_; + + my $s = IO::Socket::INET->new('127.0.0.1:' . port(8443)); + my $ssl = Net::SSLeay::new($ctx) or die("Failed to create SSL $!"); + Net::SSLeay::set_session($ssl, $extra{ses}) if $extra{ses}; + Net::SSLeay::set_cipher_list($ssl, $extra{ciphers}) if $extra{ciphers}; + Net::SSLeay::set_fd($ssl, fileno($s)); + Net::SSLeay::connect($ssl) or die("ssl connect"); + return ($s, $ssl); +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_ssl_preread.t b/tests/nginx-tests/nginx-tests/stream_ssl_preread.t index 69919020a0..787cabe6a6 100644 --- a/tests/nginx-tests/nginx-tests/stream_ssl_preread.t +++ b/tests/nginx-tests/nginx-tests/stream_ssl_preread.t @@ -35,6 +35,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + log_format status $status; map $ssl_preread_server_name $name { diff --git a/tests/nginx-tests/nginx-tests/stream_ssl_preread_alpn.t b/tests/nginx-tests/nginx-tests/stream_ssl_preread_alpn.t index eb61e34d1e..49771bb43e 100644 --- a/tests/nginx-tests/nginx-tests/stream_ssl_preread_alpn.t +++ b/tests/nginx-tests/nginx-tests/stream_ssl_preread_alpn.t @@ -35,6 +35,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + map $ssl_preread_alpn_protocols $name { "" 127.0.0.1:8093; default $ssl_preread_alpn_protocols; diff --git a/tests/nginx-tests/nginx-tests/stream_ssl_preread_protocol.t b/tests/nginx-tests/nginx-tests/stream_ssl_preread_protocol.t index 7fd1194ee6..8950577e88 100644 --- a/tests/nginx-tests/nginx-tests/stream_ssl_preread_protocol.t +++ b/tests/nginx-tests/nginx-tests/stream_ssl_preread_protocol.t @@ -23,7 +23,7 @@ select STDERR; $| = 1; select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/stream stream_ssl_preread stream_return/) - ->write_file_expand('nginx.conf', <<'EOF'); + ->write_file_expand('nginx.conf', <<'EOF')->plan(7)->run(); %%TEST_GLOBALS%% @@ -33,6 +33,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + server { listen 127.0.0.1:8080; ssl_preread on; @@ -42,8 +44,6 @@ stream { EOF -$t->try_run('no ssl_preread_protocol')->plan(7); - ############################################################################### is(get('SSLv3'), 'SSLv3', 'client hello SSLv3'); diff --git a/tests/nginx-tests/nginx-tests/stream_ssl_realip.t b/tests/nginx-tests/nginx-tests/stream_ssl_realip.t index fa62801879..004e3792bf 100644 --- a/tests/nginx-tests/nginx-tests/stream_ssl_realip.t +++ b/tests/nginx-tests/nginx-tests/stream_ssl_realip.t @@ -17,8 +17,7 @@ use Socket qw/ $CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; -use Test::Nginx; -use Test::Nginx::Stream qw/ stream /; +use Test::Nginx qw/ :DEFAULT http_end /; ############################################################################### @@ -42,6 +41,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + ssl_certificate_key localhost.key; ssl_certificate localhost.crt; @@ -128,14 +129,14 @@ like(pp_get(8088, "PROXY UNKNOWN TCP4 192.0.2.1 192.0.2.2 1234 5678${CRLF}"), sub pp_get { my ($port, $proxy) = @_; - my $s = stream(PeerPort => port($port)); - $s->write($proxy); + my $s = IO::Socket::INET->new('127.0.0.1:' . port($port)) or return; + http($proxy, start => 1, socket => $s); eval { local $SIG{ALRM} = sub { die "timeout\n" }; local $SIG{PIPE} = sub { die "sigpipe\n" }; alarm(8); - IO::Socket::SSL->start_SSL($s->{_socket}, + IO::Socket::SSL->start_SSL($s, SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(), SSL_error_trap => sub { die $_[1] } ); @@ -148,7 +149,7 @@ sub pp_get { return undef; } - return $s->read(); + http_end($s); } ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/stream_ssl_verify_client.t b/tests/nginx-tests/nginx-tests/stream_ssl_verify_client.t index 988fdc3065..f9c06e8ef9 100644 --- a/tests/nginx-tests/nginx-tests/stream_ssl_verify_client.t +++ b/tests/nginx-tests/nginx-tests/stream_ssl_verify_client.t @@ -13,8 +13,6 @@ use strict; use Test::More; -use Socket; - BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; @@ -47,6 +45,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + log_format status $status; ssl_certificate_key 1.example.com.key; @@ -140,12 +140,7 @@ is($t->read_file('status.log'), "500\n200\n", 'log'); sub get { my ($port, $cert) = @_; - my $dest_ip = inet_aton('127.0.0.1'); - my $dest_serv_params = sockaddr_in(port($port), $dest_ip); - - socket(my $s, &AF_INET, &SOCK_STREAM, 0) or die "socket: $!"; - connect($s, $dest_serv_params) or die "connect: $!"; - + my $s = IO::Socket::INET->new('127.0.0.1:' . port($port)); my $ctx = Net::SSLeay::CTX_new() or die("Failed to create SSL_CTX $!"); Net::SSLeay::set_cert_and_key($ctx, "$d/$cert.crt", "$d/$cert.key") or die if $cert; diff --git a/tests/nginx-tests/nginx-tests/stream_status_variable.t b/tests/nginx-tests/nginx-tests/stream_status_variable.t index f19803f05d..fcce862509 100644 --- a/tests/nginx-tests/nginx-tests/stream_status_variable.t +++ b/tests/nginx-tests/nginx-tests/stream_status_variable.t @@ -36,6 +36,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + log_format status $status; limit_conn_zone $binary_remote_addr zone=zone:1m; diff --git a/tests/nginx-tests/nginx-tests/stream_tcp_nodelay.t b/tests/nginx-tests/nginx-tests/stream_tcp_nodelay.t index 3b1dc23c1b..3545c601d5 100644 --- a/tests/nginx-tests/nginx-tests/stream_tcp_nodelay.t +++ b/tests/nginx-tests/nginx-tests/stream_tcp_nodelay.t @@ -38,6 +38,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_buffer_size 1; tcp_nodelay off; diff --git a/tests/nginx-tests/nginx-tests/stream_udp_limit_conn.t b/tests/nginx-tests/nginx-tests/stream_udp_limit_conn.t index bc82a9e266..b5ef4cec70 100644 --- a/tests/nginx-tests/nginx-tests/stream_udp_limit_conn.t +++ b/tests/nginx-tests/nginx-tests/stream_udp_limit_conn.t @@ -34,6 +34,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + limit_conn_zone $binary_remote_addr zone=zone:1m; limit_conn_zone $binary_remote_addr zone=zone2:1m; @@ -90,13 +92,8 @@ is($s->io('1'), '1', 'passed'); # regardless of incomplete responses, new requests in the same # socket will be treated as requests in existing session -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.15.0'); - is($s->io('1', read_timeout => 0.4), '1', 'passed new request'); -} - is(dgram('127.0.0.1:' . port(8981))->io('1', read_timeout => 0.1), '', 'rejected new session'); is(dgram('127.0.0.1:' . port(8982))->io('1'), '1', 'passed different zone'); diff --git a/tests/nginx-tests/nginx-tests/stream_udp_limit_rate.t b/tests/nginx-tests/nginx-tests/stream_udp_limit_rate.t index 89f86ff143..73f352c512 100644 --- a/tests/nginx-tests/nginx-tests/stream_udp_limit_rate.t +++ b/tests/nginx-tests/nginx-tests/stream_udp_limit_rate.t @@ -23,7 +23,7 @@ use Test::Nginx::Stream qw/ dgram /; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/stream udp/) +my $t = Test::Nginx->new()->has(qw/stream udp/)->plan(8) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -35,8 +35,11 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_requests 2; proxy_responses 1; + proxy_timeout 3s; server { listen 127.0.0.1:%%PORT_8982_UDP%% udp; @@ -59,7 +62,7 @@ stream { EOF $t->run_daemon(\&udp_daemon, port(8980), $t); -$t->try_run('no proxy_requests')->plan(8); +$t->run(); $t->waitforfile($t->testdir . '/' . port(8980)); @@ -75,23 +78,14 @@ is($s->io($str), $str, 'unlimited 2'); # datagram doesn't get split -my $t1; - -TODO: { -local $TODO = 'split datagram' unless $t->has_version('1.15.9'); - $s = dgram('127.0.0.1:' . port(8983)); is($s->io($str), $str, 'download'); -$t1 = time(); +my $t1 = time(); is($s->io($str), $str, 'download 2'); - -} - my $t2 = time(); cmp_ok($t1, '<', $t2, 'download 2 delayed'); -TODO: { -todo_skip 'infinite event report', 3 unless $t->has_version('1.15.9'); +# infinite event report before 1.15.9 $s = dgram('127.0.0.1:' . port(8984)); is($s->io($str), $str, 'upload'); @@ -100,8 +94,6 @@ is($s->io($str, read_timeout => 0.5), '', 'upload limited'); select undef, undef, undef, 1.6; is($s->io($str), $str, 'upload passed'); -} - ############################################################################### sub udp_daemon { diff --git a/tests/nginx-tests/nginx-tests/stream_udp_proxy.t b/tests/nginx-tests/nginx-tests/stream_udp_proxy.t index fb0ac799bb..51174ed1ae 100644 --- a/tests/nginx-tests/nginx-tests/stream_udp_proxy.t +++ b/tests/nginx-tests/nginx-tests/stream_udp_proxy.t @@ -22,7 +22,7 @@ use Test::Nginx::Stream qw/ dgram /; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/stream udp/)->plan(4) +my $t = Test::Nginx->new()->has(qw/stream udp/)->plan(8) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -33,6 +33,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_timeout 1s; server { @@ -75,6 +77,17 @@ is($s->io('2', read => 2), '12', 'proxy responses 2'); $s = dgram('127.0.0.1:' . port(8983)); is($s->io('3', read => 3), '123', 'proxy responses default'); +# zero-length payload + +$s = dgram('127.0.0.1:' . port(8982)); +$s->write(''); +is($s->read(), 'zero', 'upstream read zero bytes'); +is($s->read(), '', 'upstream sent zero bytes'); + +$s->write(''); +is($s->read(), 'zero', 'upstream read zero bytes again'); +is($s->read(), '', 'upstream sent zero bytes again'); + ############################################################################### sub udp_daemon { @@ -94,7 +107,15 @@ sub udp_daemon { while (1) { $server->recv(my $buffer, 65536); - $server->send($_) for (1 .. $buffer); + + if (length($buffer) > 0) { + $server->send($_) for (1 .. $buffer); + + } else { + $server->send('zero'); + select undef, undef, undef, 0.2; + $server->send(''); + } } } diff --git a/tests/nginx-tests/nginx-tests/stream_udp_proxy_requests.t b/tests/nginx-tests/nginx-tests/stream_udp_proxy_requests.t index 4f3612ea05..cc2455d6c1 100644 --- a/tests/nginx-tests/nginx-tests/stream_udp_proxy_requests.t +++ b/tests/nginx-tests/nginx-tests/stream_udp_proxy_requests.t @@ -23,7 +23,7 @@ use Test::Nginx::Stream qw/ dgram /; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/stream udp/) +my $t = Test::Nginx->new()->has(qw/stream udp/)->plan(26) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -35,6 +35,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_timeout 2100ms; log_format status $status; @@ -88,7 +90,7 @@ EOF $t->run_daemon(\&udp_daemon, $t, port(8990)); $t->run_daemon(\&udp_daemon, $t, port(8991)); -$t->try_run('no proxy_requests')->plan(26); +$t->run(); $t->waitforfile($t->testdir . '/' . port(8990)); $t->waitforfile($t->testdir . '/' . port(8991)); diff --git a/tests/nginx-tests/nginx-tests/stream_udp_stream.t b/tests/nginx-tests/nginx-tests/stream_udp_stream.t index e1943c8d41..6880e49eff 100644 --- a/tests/nginx-tests/nginx-tests/stream_udp_stream.t +++ b/tests/nginx-tests/nginx-tests/stream_udp_stream.t @@ -33,6 +33,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_timeout 1s; server { @@ -62,17 +64,12 @@ isnt($data2, '', 'udp_stream response 2'); isnt($data, $data2, 'udp_stream two sessions'); -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.15.0'); - is($s->io('1'), $data, 'udp_stream session 1'); is($s->io('1'), $data, 'udp_stream session 2'); is($s2->io('1'), $data2, 'udp_stream another session 1'); is($s2->io('1'), $data2, 'udp_stream another session 2'); -} - select undef, undef, undef, 1.1; isnt($s->io('1'), $data, 'udp_stream new session'); diff --git a/tests/nginx-tests/nginx-tests/stream_udp_upstream.t b/tests/nginx-tests/nginx-tests/stream_udp_upstream.t index a22f241420..42b6ef47dd 100644 --- a/tests/nginx-tests/nginx-tests/stream_udp_upstream.t +++ b/tests/nginx-tests/nginx-tests/stream_udp_upstream.t @@ -35,6 +35,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_responses 1; proxy_timeout 1s; @@ -96,14 +98,9 @@ my @ports = my ($port4, $port5) = (port(8984), port(8985)); is(many(10, port(8980)), "$port4: 5, $port5: 5", 'balanced'); -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.15.0'); - is(dgram('127.0.0.1:' . port(8981))->io('.', read_timeout => 0.5), '', 'no next upstream for dgram'); -} - is(many(10, port(8981)), "$port4: 5, $port5: 5", 'failures'); is(many(9, port(8982)), "$port4: 3, $port5: 6", 'weight'); diff --git a/tests/nginx-tests/nginx-tests/stream_udp_upstream_hash.t b/tests/nginx-tests/nginx-tests/stream_udp_upstream_hash.t index 9b47021426..91bcd5e67b 100644 --- a/tests/nginx-tests/nginx-tests/stream_udp_upstream_hash.t +++ b/tests/nginx-tests/nginx-tests/stream_udp_upstream_hash.t @@ -35,6 +35,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_responses 1; proxy_timeout 1s; diff --git a/tests/nginx-tests/nginx-tests/stream_udp_upstream_least_conn.t b/tests/nginx-tests/nginx-tests/stream_udp_upstream_least_conn.t index 13383533fa..bc80c72f7d 100644 --- a/tests/nginx-tests/nginx-tests/stream_udp_upstream_least_conn.t +++ b/tests/nginx-tests/nginx-tests/stream_udp_upstream_least_conn.t @@ -35,6 +35,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_responses 1; proxy_timeout 1s; diff --git a/tests/nginx-tests/nginx-tests/stream_udp_wildcard.t b/tests/nginx-tests/nginx-tests/stream_udp_wildcard.t index 202d9d8769..5ea4f402e2 100644 --- a/tests/nginx-tests/nginx-tests/stream_udp_wildcard.t +++ b/tests/nginx-tests/nginx-tests/stream_udp_wildcard.t @@ -40,6 +40,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + server { listen %%PORT_8999_UDP%% udp; return $server_addr; diff --git a/tests/nginx-tests/nginx-tests/stream_unix.t b/tests/nginx-tests/nginx-tests/stream_unix.t index 500e039c00..4bacc3f124 100644 --- a/tests/nginx-tests/nginx-tests/stream_unix.t +++ b/tests/nginx-tests/nginx-tests/stream_unix.t @@ -38,6 +38,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + upstream u { server unix:%%TESTDIR%%/unix.sock; } diff --git a/tests/nginx-tests/nginx-tests/stream_upstream.t b/tests/nginx-tests/nginx-tests/stream_upstream.t index 8e082e374c..5c0ba20f21 100644 --- a/tests/nginx-tests/nginx-tests/stream_upstream.t +++ b/tests/nginx-tests/nginx-tests/stream_upstream.t @@ -37,6 +37,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + log_format bytes $upstream_addr! $upstream_bytes_sent!$upstream_bytes_received; @@ -62,7 +64,7 @@ stream { server 127.0.0.1:8084 backup; } - proxy_connect_timeout 1s; + proxy_connect_timeout 2; server { listen 127.0.0.1:8080; diff --git a/tests/nginx-tests/nginx-tests/stream_upstream_hash.t b/tests/nginx-tests/nginx-tests/stream_upstream_hash.t index 885c50faea..12a8f00cb7 100644 --- a/tests/nginx-tests/nginx-tests/stream_upstream_hash.t +++ b/tests/nginx-tests/nginx-tests/stream_upstream_hash.t @@ -25,7 +25,7 @@ use Test::Nginx::Stream qw/ stream /; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/stream stream_upstream_hash/)->plan(2); +my $t = Test::Nginx->new()->has(qw/stream stream_upstream_hash/)->plan(4); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -37,6 +37,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + upstream hash { hash $remote_addr; server 127.0.0.1:8082; @@ -49,6 +51,18 @@ stream { server 127.0.0.1:8083; } + upstream empty { + hash $proxy_protocol_addr; + server 127.0.0.1:8082; + server 127.0.0.1:8083; + } + + upstream cempty { + hash $proxy_protocol_addr consistent; + server 127.0.0.1:8082; + server 127.0.0.1:8083; + } + server { listen 127.0.0.1:8080; proxy_pass hash; @@ -58,6 +72,16 @@ stream { listen 127.0.0.1:8081; proxy_pass cons; } + + server { + listen 127.0.0.1:8084; + proxy_pass empty; + } + + server { + listen 127.0.0.1:8085; + proxy_pass cempty; + } } EOF @@ -76,6 +100,11 @@ my @ports = my ($port2, $port3) = (port(8082), port(8083)); is(many(10, port(8080)), "$port3: 10", 'hash'); like(many(10, port(8081)), qr/($port2|$port3): 10/, 'hash consistent'); +# fallback to round-robin + +like(many(4, port(8084)), qr/$port2: 2, $port3: 2/, 'empty key'); +like(many(4, port(8085)), qr/$port2: 2, $port3: 2/, 'empty key - consistent'); + ############################################################################### sub many { diff --git a/tests/nginx-tests/nginx-tests/stream_upstream_least_conn.t b/tests/nginx-tests/nginx-tests/stream_upstream_least_conn.t index 24a1842a93..4060ee311c 100644 --- a/tests/nginx-tests/nginx-tests/stream_upstream_least_conn.t +++ b/tests/nginx-tests/nginx-tests/stream_upstream_least_conn.t @@ -37,6 +37,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + upstream u { least_conn; server 127.0.0.1:8081; diff --git a/tests/nginx-tests/nginx-tests/stream_upstream_max_conns.t b/tests/nginx-tests/nginx-tests/stream_upstream_max_conns.t index 2eae885622..40c65ab1ae 100644 --- a/tests/nginx-tests/nginx-tests/stream_upstream_max_conns.t +++ b/tests/nginx-tests/nginx-tests/stream_upstream_max_conns.t @@ -39,6 +39,8 @@ events { worker_processes 1; # NOTE: The default value of Tengine worker_processes directive is `worker_processes auto;`. stream { + %%TEST_GLOBALS_STREAM%% + upstream u_unlim { server 127.0.0.1:8081 max_conns=0; server 127.0.0.1:8082; diff --git a/tests/nginx-tests/nginx-tests/stream_upstream_random.t b/tests/nginx-tests/nginx-tests/stream_upstream_random.t index 87df8c61f6..ac59a965e5 100644 --- a/tests/nginx-tests/nginx-tests/stream_upstream_random.t +++ b/tests/nginx-tests/nginx-tests/stream_upstream_random.t @@ -23,7 +23,7 @@ select STDERR; $| = 1; select STDOUT; $| = 1; my $t = Test::Nginx->new() - ->has(qw/stream stream_upstream_zone stream_upstream_random/) + ->has(qw/stream stream_upstream_zone stream_upstream_random/)->plan(12) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -35,6 +35,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + upstream u { zone z 1m; random; @@ -108,6 +110,8 @@ stream { server 127.0.0.1:8082; } + proxy_connect_timeout 2; + server { listen 127.0.0.1:8080; proxy_pass u; @@ -153,8 +157,6 @@ stream { proxy_pass ztwo; } - proxy_connect_timeout 1s; - server { listen 127.0.0.1:8091; proxy_pass fail; @@ -170,7 +172,7 @@ EOF $t->run_daemon(\&http_daemon, port(8081)); $t->run_daemon(\&http_daemon, port(8082)); -$t->try_run('no upstream random')->plan(12); +$t->run(); $t->waitforsocket('127.0.0.1:' . port(8081)); $t->waitforsocket('127.0.0.1:' . port(8082)); @@ -245,7 +247,8 @@ sub parallel { my @s = map { get($port, $uri, start => 1, sleep => 0.1) } (1 .. $n); for (@s) { - if (http_end($_) =~ /X-Port: (\d+)/) { + my $r = http_end($_); + if ($r && $r =~ /X-Port: (\d+)/) { $ports{$1} = 0 unless defined $ports{$1}; $ports{$1}++; } diff --git a/tests/nginx-tests/nginx-tests/stream_upstream_zone.t b/tests/nginx-tests/nginx-tests/stream_upstream_zone.t index 77a99d9140..03a88fd53f 100644 --- a/tests/nginx-tests/nginx-tests/stream_upstream_zone.t +++ b/tests/nginx-tests/nginx-tests/stream_upstream_zone.t @@ -34,6 +34,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + log_format test $upstream_addr; upstream u { @@ -76,8 +78,8 @@ $t->run(); my $p = port(8081); -stream('127.0.0.1:' . port(8091)); -stream("127.0.0.1:" . port(8092)); +stream('127.0.0.1:' . port(8091))->read(); +stream("127.0.0.1:" . port(8092))->read(); $t->stop(); diff --git a/tests/nginx-tests/nginx-tests/stream_upstream_zone_ssl.t b/tests/nginx-tests/nginx-tests/stream_upstream_zone_ssl.t index f17d5ef7a0..3af8373b12 100644 --- a/tests/nginx-tests/nginx-tests/stream_upstream_zone_ssl.t +++ b/tests/nginx-tests/nginx-tests/stream_upstream_zone_ssl.t @@ -36,6 +36,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + proxy_ssl on; proxy_ssl_session_reuse on; diff --git a/tests/nginx-tests/nginx-tests/stream_variables.t b/tests/nginx-tests/nginx-tests/stream_variables.t index 2696c3547e..11e146696b 100644 --- a/tests/nginx-tests/nginx-tests/stream_variables.t +++ b/tests/nginx-tests/nginx-tests/stream_variables.t @@ -12,6 +12,8 @@ use strict; use Test::More; +use Sys::Hostname; + BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; @@ -35,6 +37,8 @@ events { } stream { + %%TEST_GLOBALS_STREAM%% + server { listen 127.0.0.1:8080; return $connection:$nginx_version:$hostname:$pid:$bytes_sent; @@ -80,7 +84,7 @@ $t->try_run('no inet6 support')->plan(8); ############################################################################### -chomp(my $hostname = lc `hostname`); +my $hostname = lc hostname(); like(stream('127.0.0.1:' . port(8080))->read(), qr/^\d+:[\d.]+:$hostname:\d+:0$/, 'vars'); diff --git a/tests/nginx-tests/nginx-tests/sub_filter_buffering.t b/tests/nginx-tests/nginx-tests/sub_filter_buffering.t index 43cf97a300..f3631a33ca 100644 --- a/tests/nginx-tests/nginx-tests/sub_filter_buffering.t +++ b/tests/nginx-tests/nginx-tests/sub_filter_buffering.t @@ -99,3 +99,5 @@ sub http_daemon { "xyz"; } } + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/sub_filter_slice.t b/tests/nginx-tests/nginx-tests/sub_filter_slice.t index 5277e634bd..4f1625044e 100644 --- a/tests/nginx-tests/nginx-tests/sub_filter_slice.t +++ b/tests/nginx-tests/nginx-tests/sub_filter_slice.t @@ -17,7 +17,7 @@ use Test::More; BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; -use Test::Nginx; +use Test::Nginx qw/ :DEFAULT http_content /; ############################################################################### @@ -83,7 +83,7 @@ local $TODO = 'not yet'; $r = get('/t', 'Range: bytes=3-4'); like($r, qr/ 206 /, 'range request - 206 partial reply'); -is(Test::Nginx::http_content($r), '34', 'range request - correct content'); +is(http_content($r), '34', 'range request - correct content'); } diff --git a/tests/nginx-tests/nginx-tests/syslog.t b/tests/nginx-tests/nginx-tests/syslog.t index 4d76b5506e..977fffab8e 100644 --- a/tests/nginx-tests/nginx-tests/syslog.t +++ b/tests/nginx-tests/nginx-tests/syslog.t @@ -13,6 +13,7 @@ use strict; use Test::More; use IO::Select; +use Sys::Hostname; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -26,7 +27,7 @@ select STDOUT; $| = 1; plan(skip_all => 'win32') if $^O eq 'MSWin32'; -my $t = Test::Nginx->new()->has(qw/http limit_req/)->plan(61); +my $t = Test::Nginx->new()->has(qw/http limit_req/)->plan(62); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -221,7 +222,8 @@ http_get('/if/work?logme=yes'); get_syslog('/a'); -like($t->read_file('s_if.log'), qr/good:404.*work:404/s, 'syslog if success'); +like($t->read_file('s_if.log'), qr/good:404/s, 'syslog if success'); +like($t->read_file('s_if.log'), qr/work:404/s, 'syslog if success 2'); unlike($t->read_file('s_if.log'), qr/(if:|empty:|zero:)404/, 'syslog if fail'); like(get_syslog('/nohostname'), @@ -321,8 +323,7 @@ sub parse_syslog_message { ok($sec < 60, "$desc valid seconds"); ok(defined($host), "$desc has host"); - chomp(my $hostname = lc `hostname`); - is($host , $hostname, "$desc valid host"); + is($host, lc(hostname()), "$desc valid host"); ok(defined($tag), "$desc has tag"); like($tag, qr'\w+', "$desc valid tag"); diff --git a/tests/nginx-tests/nginx-tests/trailers.t b/tests/nginx-tests/nginx-tests/trailers.t new file mode 100644 index 0000000000..aa220c4cc3 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/trailers.t @@ -0,0 +1,134 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for trailers in headers filter module. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use Socket qw/ $CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(17) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + add_trailer X-Var $host; + add_trailer X-Always $host always; + add_trailer X-Empty ''; + add_trailer X-Sent-HTTP $sent_http_accept_ranges; + add_trailer X-Sent-Trailer $sent_trailer_x_var; + add_trailer X-Complex $host:$host; + + location /t1 { + } + + location /nx { + } + + location /header { + add_header X-Var foo; + } + + location /empty { + add_trailer X-Var $host; + } + + location /not_chunked { + chunked_transfer_encoding off; + } + + location /proxy { + proxy_pass http://127.0.0.1:8080/t1; + add_trailer X-Length $upstream_response_length; + } + } +} + +EOF + +$t->write_file('t1', 'SEE-THIS'); +$t->write_file('header', ''); +$t->run(); + +############################################################################### + +my $r; + +$r = get('/t1'); +like($r, qr/8${CRLF}SEE-THIS${CRLF}0${CRLF}(.+${CRLF}){5}$CRLF/, 'trailers'); +unlike($r, qr/X-Var.*SEE-THIS/s, 'not in headers'); +like($r, qr/X-Var: localhost/, 'add_trailer'); +like($r, qr/X-Always/, 'add_trailer always'); +like($r, qr/X-Sent-HTTP: bytes/, 'add_trailer sent_http'); +like($r, qr/X-Sent-Trailer: localhost/, 'add_trailer sent_trailer'); +like($r, qr/X-Complex: localhost:localhost/, 'add_trailer complex'); +unlike($r, qr/X-Empty/, 'add_trailer empty'); + +$r = get('/nx'); +unlike($r, qr/X-Var/, 'add_trailer bad'); +like($r, qr/X-Always/, 'add_trailer bad always'); + +like(get('/header'), qr/foo.*^0$CRLF.*X-Var: localhost/ms, 'header name'); + +like(http_get('/t1'), qr/${CRLF}SEE-THIS$/, 'no trailers - http10'); +unlike(get('/not_chunked'), qr/X-Always/, 'no trailers - not chunked'); +unlike(head('/t1'), qr/X-Always/, 'no trailers - head'); + +unlike(get('/empty'), qr/X-Var/, 'no trailers expected'); + +$r = get('/proxy'); +like($r, qr/SEE-THIS.*X-Length: 8/ms, 'upstream response variable'); +unlike($r, qr/X-Var/, 'inheritance'); + +############################################################################### + +sub get { + my ($uri) = @_; + http(<new()->has(qw/http proxy upstream_ip_hash realip unix/) - ->write_file_expand('nginx.conf', <<'EOF')->run(); + ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -95,10 +95,12 @@ http { EOF +$t->try_run('no inet6 support'); + plan(skip_all => 'no 127.0.0.1 on host') if http_get('/') !~ /X-IP: 127.0.0.1/m; -$t->try_run('no inet6 support')->plan(4); +$t->plan(4); ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/upstream_keepalive.t b/tests/nginx-tests/nginx-tests/upstream_keepalive.t index 7aa311414b..f80de4ee5e 100644 --- a/tests/nginx-tests/nginx-tests/upstream_keepalive.t +++ b/tests/nginx-tests/nginx-tests/upstream_keepalive.t @@ -3,7 +3,7 @@ # (C) Sergey Kandaurov # (C) Nginx, Inc. -# Tests for upstream keepalive, keepalive_requests and keepalive_timeout. +# Tests for upstream keepalive directives. ############################################################################### @@ -43,6 +43,12 @@ http { keepalive_timeout 2s; } + upstream time { + server 127.0.0.1:8081; + keepalive 1; + keepalive_time 2s; + } + server { listen 127.0.0.1:8080; server_name localhost; @@ -53,6 +59,10 @@ http { location / { proxy_pass http://backend; } + + location /time { + proxy_pass http://time/; + } } server { @@ -68,11 +78,11 @@ http { EOF $t->write_file('index.html', 'SEE-THIS'); -$t->try_run('no keepalive_requests')->plan(7); +$t->run()->plan(11); ############################################################################### -my ($r, $n); +my ($r, $n, $m); # keepalive_requests @@ -83,12 +93,20 @@ like(http_get('/'), qr/X-Connection: $n.*SEE/ms, 'keepalive again'); like(http_get('/'), qr/X-Connection: (?!$n).*SEE/ms, 'keepalive requests'); http_get('/?close'); -# keepalive_timeout +# keepalive_timeout, keepalive_time like($r = http_get('/'), qr/SEE-THIS/, 'request timer'); $r =~ m/X-Connection: (\d+)/; $n = $1; +like($r = http_get('/time'), qr/SEE-THIS/, 'request time'); +$r =~ m/X-Connection: (\d+)/; $m = $1; + like(http_get('/'), qr/X-Connection: $n.*SEE/ms, 'keepalive timer'); +like(http_get('/time'), qr/X-Connection: $m.*SEE/ms, 'keepalive time'); + select undef, undef, undef, 2.5; + like(http_get('/'), qr/X-Connection: (?!$n).*SEE/ms, 'keepalive timeout'); +like(http_get('/time'), qr/X-Connection: $m.*SEE/ms, 'keepalive time last'); +like(http_get('/time'), qr/X-Connection: (?!$m).*SEE/ms, 'keepalive time new'); ############################################################################### diff --git a/tests/nginx-tests/nginx-tests/upstream_random.t b/tests/nginx-tests/nginx-tests/upstream_random.t index 73713d81ea..67e8ddf350 100644 --- a/tests/nginx-tests/nginx-tests/upstream_random.t +++ b/tests/nginx-tests/nginx-tests/upstream_random.t @@ -23,7 +23,7 @@ select STDERR; $| = 1; select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/http proxy upstream_zone upstream_random/) - ->write_file_expand('nginx.conf', <<'EOF'); + ->plan(12)->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -140,7 +140,7 @@ EOF $t->run_daemon(\&http_daemon, port(8081)); $t->run_daemon(\&http_daemon, port(8082)); -$t->try_run('no upstream random')->plan(12); +$t->run(); $t->waitforsocket('127.0.0.1:' . port(8081)); $t->waitforsocket('127.0.0.1:' . port(8082)); diff --git a/tests/nginx-tests/nginx-tests/userid.t b/tests/nginx-tests/nginx-tests/userid.t index ce4af48605..163ba71fa7 100644 --- a/tests/nginx-tests/nginx-tests/userid.t +++ b/tests/nginx-tests/nginx-tests/userid.t @@ -194,10 +194,7 @@ is(get_cookie('/expires_off', 'expires'), undef, 'expires off'); # redefinition -SKIP: { -skip 'the page include req url to cause the error match, when the request returns 4xx'; unlike(http_get('/expires_max/off'), qr/expires/, 'redefine expires'); -} like(http_get('/path/r'), qr!/9876543210!, 'redefine path'); # requests @@ -230,13 +227,8 @@ is(substr(uid_set(http_get('/service')), 0, 8), $addr, 'service custom'); $addr = $bigendian ? "00000001" : "01000000"; is(substr(uid_set(http_get('/ip6')), 0, 8), $addr, 'service ipv6'); -TODO: { -local $TODO = 'not yet' unless $t->has_version('1.15.8'); - is(substr(uid_set(http_get('/unix')), 0, 8), "00000000", 'service unix'); -} - # reset log send_uid('/?log', cookie($r, 'uid')); diff --git a/tests/nginx-tests/nginx-tests/userid_flags.t b/tests/nginx-tests/nginx-tests/userid_flags.t new file mode 100644 index 0000000000..9214d7f49d --- /dev/null +++ b/tests/nginx-tests/nginx-tests/userid_flags.t @@ -0,0 +1,83 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for the userid_flags directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http userid/); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + userid on; + userid_name test; + userid_path /0123456789; + userid_domain test.domain; + + location / { + userid_flags samesite=strict; + + location /many { + userid_flags httponly samesite=none secure; + } + + location /off { + userid_flags off; + } + } + + location /lax { + userid_flags samesite=lax; + } + + location /unset { } + } +} + +EOF + +$t->write_file('index.html', ''); +$t->write_file('lax', ''); +$t->write_file('many', ''); +$t->run()->plan(5); + +############################################################################### + +like(http_get('/'), qr/samesite=strict/i, 'strict'); +like(http_get('/lax'), qr/samesite=lax/i, 'lax'); +like(http_get('/many'), qr/secure; httponly; samesite=none/i, 'many'); +unlike(http_get('/off'), qr/(secure|httponly|samesite)/i, 'off'); +unlike(http_get('/unset'), qr/(secure|httponly|samesite)/i, 'unset'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/uwsgi_body.t b/tests/nginx-tests/nginx-tests/uwsgi_body.t new file mode 100644 index 0000000000..d8857c879c --- /dev/null +++ b/tests/nginx-tests/nginx-tests/uwsgi_body.t @@ -0,0 +1,129 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Test for uwsgi backend with request body. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite uwsgi/) + ->has_daemon('uwsgi')->plan(5) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + set $variable $content_length; + + location / { + uwsgi_pass 127.0.0.1:8081; + uwsgi_param CONTENT_LENGTH $content_length if_not_empty; + } + } +} + +EOF + +$t->write_file('uwsgi_test_app.py', <&", \*STDERR; close STDERR; +$t->run_daemon('uwsgi', '--socket', '127.0.0.1:' . port(8081), @uwsgiopts, + '--wsgi-file', $t->testdir() . '/uwsgi_test_app.py', + '--logto', $t->testdir() . '/uwsgi_log'); +open STDERR, ">&", \*OLDERR; + +$t->run(); + +$t->waitforsocket('127.0.0.1:' . port(8081)) + or die "Can't start uwsgi"; + +############################################################################### + +like(http_get('/'), qr/SEE-THIS/, 'uwsgi no body'); + +like(http_get_length('/', 'foobar'), qr/cl=6 'foobar'/, 'uwsgi body'); +like(http_get_length('/', ''), qr/cl=0 ''/, 'uwsgi empty body'); + +# rewrite set is used to cache $content_length early + +like(http_get_chunked('/', 'foobar'), qr/cl=6 'foobar'/, 'uwsgi chunked'); +like(http_get_chunked('/', ''), qr/cl=0 ''/, 'uwsgi empty chunked'); + +############################################################################### + +sub http_get_length { + my ($url, $body) = @_; + my $length = length $body; + return http(<new()->has(qw/http http_ssl uwsgi/) + ->has_daemon('openssl'); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + uwsgi_ssl_session_reuse off; + + location / { + uwsgi_pass suwsgi://127.0.0.1:8081; + uwsgi_ssl_certificate $arg_cert.example.com.crt; + uwsgi_ssl_certificate_key $arg_cert.example.com.key; + } + + location /encrypted { + uwsgi_pass suwsgi://127.0.0.1:8082; + uwsgi_ssl_certificate $arg_cert.example.com.crt; + uwsgi_ssl_certificate_key $arg_cert.example.com.key; + uwsgi_ssl_password_file password; + } + + location /none { + uwsgi_pass suwsgi://127.0.0.1:8082; + uwsgi_ssl_certificate $arg_cert; + uwsgi_ssl_certificate_key $arg_cert; + } + } + + # stub to implement SSL logic for tests + + server { + listen 127.0.0.1:8081 ssl; + server_name localhost; + + ssl_certificate 2.example.com.crt; + ssl_certificate_key 2.example.com.key; + + ssl_verify_client optional_no_ca; + ssl_trusted_certificate 1.example.com.crt; + + add_header X-Verify $ssl_client_verify always; + add_header X-Name $ssl_client_s_dn always; + } + + server { + listen 127.0.0.1:8082 ssl; + server_name localhost; + + ssl_certificate 1.example.com.crt; + ssl_certificate_key 1.example.com.key; + + ssl_verify_client optional_no_ca; + ssl_trusted_certificate 3.example.com.crt; + + add_header X-Verify $ssl_client_verify always; + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('1.example.com', '2.example.com') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +foreach my $name ('3.example.com') { + system("openssl genrsa -out $d/$name.key -passout pass:$name " + . "-aes128 2048 >>$d/openssl.out 2>&1") == 0 + or die "Can't create private key: $!\n"; + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt " + . "-key $d/$name.key -passin pass:$name" + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +sleep 1 if $^O eq 'MSWin32'; + +$t->write_file('password', '3.example.com'); +$t->write_file('index.html', ''); + +$t->try_run('no upstream ssl_certificate variables')->plan(4); + +############################################################################### + +like(http_get('/?cert=1'), + qr/X-Verify: SUCCESS/ms, 'variable - verify certificate'); +like(http_get('/?cert=2'), + qr/X-Verify: FAILED/ms, 'variable - fail certificate'); +like(http_get('/encrypted?cert=3'), + qr/X-Verify: SUCCESS/ms, 'variable - with encrypted key'); +like(http_get('/none'), + qr/X-Verify: NONE/ms, 'variable - no certificate'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/worker_shutdown_timeout.t b/tests/nginx-tests/nginx-tests/worker_shutdown_timeout.t new file mode 100644 index 0000000000..9527c11f74 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/worker_shutdown_timeout.t @@ -0,0 +1,67 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for worker_shutdown_timeout directive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use IO::Select; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; +worker_shutdown_timeout 10ms; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { } + } +} + +EOF + +$t->run()->plan(1); + +############################################################################### + +my $s = http('', start => 1); + +select undef, undef, undef, 0.2; + +$t->reload(); + +if (IO::Select->new($s)->can_read(5)) { + Test::Nginx::log_core('||', "select: can_read"); +} + +is(http_get('/', socket => $s) || '', '', 'worker_shutdown_timeout'); + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/worker_shutdown_timeout_h2.t b/tests/nginx-tests/nginx-tests/worker_shutdown_timeout_h2.t new file mode 100644 index 0000000000..d217289df4 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/worker_shutdown_timeout_h2.t @@ -0,0 +1,86 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for worker_shutdown_timeout and HTTP/2 with proxy. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::HTTP2; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_v2 proxy/)->plan(2); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; +worker_shutdown_timeout 10ms; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080 http2; + server_name localhost; + + location / { + proxy_pass http://127.0.0.1:8081; + proxy_read_timeout 5s; + } + } +} +EOF + +$t->run_daemon(\&http_silent_daemon); +$t->run()->waitforsocket('127.0.0.1:' . port(8081)); + +############################################################################### + +my $s = Test::Nginx::HTTP2->new(); +ok($s->new_stream(), 'new stream'); + +$s->h2_ping('SEE-THIS'); +$s->read(all => [{ type => 'PING' }]); + +$t->stop(); + +like($t->read_file('access.log'), qr/ (?!504)\d{3} /, 'shutdown timeout'); + +############################################################################### + +sub http_silent_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . port(8081), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + while (<$client>) { } + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/worker_shutdown_timeout_mail.t b/tests/nginx-tests/nginx-tests/worker_shutdown_timeout_mail.t new file mode 100644 index 0000000000..4c6a162263 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/worker_shutdown_timeout_mail.t @@ -0,0 +1,93 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for worker_shutdown_timeout directive within the mail module. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use MIME::Base64; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::SMTP; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +local $SIG{PIPE} = 'IGNORE'; + +my $t = Test::Nginx->new()->has(qw/mail imap http rewrite/)->plan(4) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; +worker_shutdown_timeout 10ms; + +events { +} + +mail { + proxy_pass_error_message on; + proxy_timeout 15s; + auth_http http://127.0.0.1:8080/mail/auth; + xclient off; + + server { + listen 127.0.0.1:8025; + protocol smtp; + } +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location = /mail/auth { + add_header Auth-Status OK; + add_header Auth-Server 127.0.0.1; + add_header Auth-Port %%PORT_8026%%; + add_header Auth-Wait 1; + return 204; + } + } +} + +EOF + +$t->run_daemon(\&Test::Nginx::SMTP::smtp_test_daemon); +$t->run()->waitforsocket('127.0.0.1:' . port(8026)); + +############################################################################### + +my $s = Test::Nginx::SMTP->new(); +$s->check(qr/^220 /, "greeting"); + +$s->send('EHLO example.com'); +$s->check(qr/^250 /, "ehlo"); + +$s->send('AUTH PLAIN ' . encode_base64("\0test\@example.com\0secret", '')); +$s->authok('auth plain'); + +$t->reload(); + +ok($s->can_read(), 'mail connection shutdown'); + +undef $s; +1; + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/worker_shutdown_timeout_proxy_upgrade.t b/tests/nginx-tests/nginx-tests/worker_shutdown_timeout_proxy_upgrade.t new file mode 100644 index 0000000000..64d1a67a16 --- /dev/null +++ b/tests/nginx-tests/nginx-tests/worker_shutdown_timeout_proxy_upgrade.t @@ -0,0 +1,121 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for worker_shutdown_timeout directive with http proxy upgrade stub. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use IO::Select; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(2) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; +worker_shutdown_timeout 10ms; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location / { + proxy_pass http://127.0.0.1:8081; + proxy_http_version 1.1; + proxy_set_header Upgrade foo; + proxy_set_header Connection Upgrade; + } + } +} + +EOF + +$t->run_daemon(\&http_daemon); +$t->run()->waitforsocket('127.0.0.1:' . port(8081)); + +############################################################################### + +my $s = http(< 1); +GET / HTTP/1.1 +Host: localhost +Upgrade: foo +Connection: Upgrade + +EOF + +my ($sel, $buf) = IO::Select->new($s); +if ($sel->can_read(5)) { + $s->sysread($buf, 1024); + log_in($buf); +}; + +like($buf, qr!HTTP/1.1 101!, 'upgraded connection'); + +$t->reload(); + +ok($sel->can_read(3), 'upgraded connection shutdown'); + +undef $s; + +############################################################################### + +sub http_daemon { + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => '127.0.0.1:' . port(8081), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + my $client; + + while ($client = $server->accept()) { + $client->autoflush(1); + + my $headers = ''; + my $uri = ''; + + while (<$client>) { + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + next if $headers eq ''; + + print $client <<'EOF'; +HTTP/1.1 101 Switching +Upgrade: foo +Connection: Upgrade + +EOF + + } +} + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/worker_shutdown_timeout_stream.t b/tests/nginx-tests/nginx-tests/worker_shutdown_timeout_stream.t new file mode 100644 index 0000000000..14308ae9db --- /dev/null +++ b/tests/nginx-tests/nginx-tests/worker_shutdown_timeout_stream.t @@ -0,0 +1,68 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for worker_shutdown_timeout directive within the stream module. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::SMTP; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +local $SIG{PIPE} = 'IGNORE'; + +my $t = Test::Nginx->new()->has(qw/stream/)->plan(3) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; +worker_shutdown_timeout 10ms; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + server { + listen 127.0.0.1:8025; + proxy_pass 127.0.0.1:8026; + } +} + +EOF + +$t->run_daemon(\&Test::Nginx::SMTP::smtp_test_daemon); +$t->run()->waitforsocket('127.0.0.1:' . port(8026)); + +############################################################################### + +my $s = Test::Nginx::SMTP->new(); +$s->check(qr/^220 /, "greeting"); + +$s->send('EHLO example.com'); +$s->check(qr/^250 /, "ehlo"); + +$t->reload(); + +ok($s->can_read(), 'stream connection shutdown'); + +undef $s; +1; + +############################################################################### diff --git a/tests/nginx-tests/nginx-tests/xslt.t b/tests/nginx-tests/nginx-tests/xslt.t index c50c3f2948..b3b37d0092 100644 --- a/tests/nginx-tests/nginx-tests/xslt.t +++ b/tests/nginx-tests/nginx-tests/xslt.t @@ -21,7 +21,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http xslt/)->plan(5); +my $t = Test::Nginx->new()->has(qw/http xslt/)->plan(8); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -56,6 +56,10 @@ http { xslt_stylesheet %%TESTDIR%%/first.xslt; xslt_stylesheet %%TESTDIR%%/test.xslt; } + location /x5 { + xslt_stylesheet %%TESTDIR%%/test.xslt + param1='$server_name'; + } } } @@ -102,6 +106,7 @@ $t->write_file('x1', ''); $t->write_file('x2', 'data'); $t->write_file('x3', '&test;'); $t->write_file('x4', 'data'); +$t->write_file('x5', 'data'); $t->run(); @@ -113,5 +118,17 @@ like(http_get("/x2"), qr!200 OK.*param1=value1.*param2=data.*param3=value3!ms, 'params'); like(http_get("/x3"), qr!200 OK.*data=test entity!ms, 'entities'); like(http_get("/x4"), qr!200 OK.*data=other data!ms, 'several stylesheets'); +like(http_get("/x5"), qr!200 OK.*param1=localhost!ms, 'params variable'); + +# xslt and ranges + +unlike(http_get("/x1"), qr!Accept-Ranges!, 'no Accept-Ranges'); +like(http(<has_version('1.13.7'); - like(http_get("/x1"), qr!200 OK.*param1=value1.*param2=data.*param3=value3!ms, 'params from xslt_stylesheet again'); -} - like(http_get("/x2"), qr!200 OK.*param1=value1.*param2=data.*param3=value3!ms, 'params from xslt_param/xslt_string_param'); like(http_get("/x3"), qr!200 OK.*param1=value1.*param2=data.*param3=value3!ms, diff --git a/tests/nginx-tests/tengine-tests/if_numeric_comparison.t b/tests/nginx-tests/tengine-tests/if_numeric_comparison.t index 0163cd75f4..9249de6d46 100644 --- a/tests/nginx-tests/tengine-tests/if_numeric_comparison.t +++ b/tests/nginx-tests/tengine-tests/if_numeric_comparison.t @@ -245,34 +245,34 @@ like(http_get('/t6?b=123'), qr/^HTTP.*200/, ############################################################################### -like(http_get('/t1?a=123456 &foo=bar'), qr/^HTTP.*200/, +like(http_get('/t1?a=123456 &foo=bar'), qr/^HTTP.*400/, "ngx_atoi error due to space, get false"); -like(http_get('/t2?a=123456 &foo=bar'), qr/^HTTP.*200/, +like(http_get('/t2?a=123456 &foo=bar'), qr/^HTTP.*400/, "ngx_atoi error due to space, get false"); -like(http_get('/t3?a=123456 &b=1&foo=bar'), qr/^HTTP.*200/, +like(http_get('/t3?a=123456 &b=1&foo=bar'), qr/^HTTP.*400/, "ngx_atoi error due to space, get false"); -like(http_get('/t3?a=1&b=123456 &foo=bar'), qr/^HTTP.*200/, +like(http_get('/t3?a=1&b=123456 &foo=bar'), qr/^HTTP.*400/, "ngx_atoi error due to space, get false"); -like(http_get('/t4?a=123456 &b=1&foo=bar'), qr/^HTTP.*200/, +like(http_get('/t4?a=123456 &b=1&foo=bar'), qr/^HTTP.*400/, "ngx_atoi error due to space, get false"); -like(http_get('/t4?a=1&b=123456 &foo=bar'), qr/^HTTP.*200/, +like(http_get('/t4?a=1&b=123456 &foo=bar'), qr/^HTTP.*400/, "ngx_atoi error due to space, get false"); -like(http_get('/t5?a=123456 &b=1&foo=bar'), qr/^HTTP.*200/, +like(http_get('/t5?a=123456 &b=1&foo=bar'), qr/^HTTP.*400/, "ngx_atoi error due to space, get false"); -like(http_get('/t5?a=1&b=123456 &foo=bar'), qr/^HTTP.*200/, +like(http_get('/t5?a=1&b=123456 &foo=bar'), qr/^HTTP.*400/, "ngx_atoi error due to space, get false"); -like(http_get('/t6?a=123456 &b=1&foo=bar'), qr/^HTTP.*200/, +like(http_get('/t6?a=123456 &b=1&foo=bar'), qr/^HTTP.*400/, "ngx_atoi error due to space, get false"); -like(http_get('/t6?a=1&b=123456 &foo=bar'), qr/^HTTP.*200/, +like(http_get('/t6?a=1&b=123456 &foo=bar'), qr/^HTTP.*400/, "ngx_atoi error due to space, get false");